001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.blocks;
021
022import java.util.Locale;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030
031/**
032 * <p>
033 * Checks the placement of right curly braces.
034 * The policy to verify is specified using the {@link RightCurlyOption} class
035 * and defaults to {@link RightCurlyOption#SAME}.
036 * </p>
037 * <p> By default the check will check the following tokens:
038 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
039 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
040 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
041 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
042 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
043 * Other acceptable tokens are:
044 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
045 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
046 *  {@link TokenTypes#CTOR_DEF CTOR_DEF}.
047 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
048 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
049 *  {@link TokenTypes#LITERAL_DO LITERAL_DO}.
050 *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
051 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
052 * </p>
053 * <p>
054 * An example of how to configure the check is:
055 * </p>
056 * <pre>
057 * &lt;module name="RightCurly"/&gt;
058 * </pre>
059 * <p>
060 * An example of how to configure the check with policy
061 * {@link RightCurlyOption#ALONE} for {@code else} and
062 * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is:
063 * </p>
064 * <pre>
065 * &lt;module name="RightCurly"&gt;
066 *     &lt;property name="tokens" value="LITERAL_ELSE"/&gt;
067 *     &lt;property name="option" value="alone"/&gt;
068 * &lt;/module&gt;
069 * </pre>
070 *
071 */
072@StatelessCheck
073public class RightCurlyCheck extends AbstractCheck {
074
075    /**
076     * A key is pointing to the warning message text in "messages.properties"
077     * file.
078     */
079    public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
080
081    /**
082     * A key is pointing to the warning message text in "messages.properties"
083     * file.
084     */
085    public static final String MSG_KEY_LINE_ALONE = "line.alone";
086
087    /**
088     * A key is pointing to the warning message text in "messages.properties"
089     * file.
090     */
091    public static final String MSG_KEY_LINE_SAME = "line.same";
092
093    /** The policy to enforce. */
094    private RightCurlyOption option = RightCurlyOption.SAME;
095
096    /**
097     * Sets the option to enforce.
098     * @param optionStr string to decode option from
099     * @throws IllegalArgumentException if unable to decode
100     */
101    public void setOption(String optionStr) {
102        option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
103    }
104
105    @Override
106    public int[] getDefaultTokens() {
107        return new int[] {
108            TokenTypes.LITERAL_TRY,
109            TokenTypes.LITERAL_CATCH,
110            TokenTypes.LITERAL_FINALLY,
111            TokenTypes.LITERAL_IF,
112            TokenTypes.LITERAL_ELSE,
113        };
114    }
115
116    @Override
117    public int[] getAcceptableTokens() {
118        return new int[] {
119            TokenTypes.LITERAL_TRY,
120            TokenTypes.LITERAL_CATCH,
121            TokenTypes.LITERAL_FINALLY,
122            TokenTypes.LITERAL_IF,
123            TokenTypes.LITERAL_ELSE,
124            TokenTypes.CLASS_DEF,
125            TokenTypes.METHOD_DEF,
126            TokenTypes.CTOR_DEF,
127            TokenTypes.LITERAL_FOR,
128            TokenTypes.LITERAL_WHILE,
129            TokenTypes.LITERAL_DO,
130            TokenTypes.STATIC_INIT,
131            TokenTypes.INSTANCE_INIT,
132        };
133    }
134
135    @Override
136    public int[] getRequiredTokens() {
137        return CommonUtil.EMPTY_INT_ARRAY;
138    }
139
140    @Override
141    public void visitToken(DetailAST ast) {
142        final Details details = Details.getDetails(ast);
143        final DetailAST rcurly = details.rcurly;
144
145        if (rcurly != null) {
146            final String violation = validate(details);
147            if (!violation.isEmpty()) {
148                log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
149            }
150        }
151    }
152
153    /**
154     * Does general validation.
155     * @param details for validation.
156     * @return violation message or empty string
157     *     if there was not violation during validation.
158     */
159    private String validate(Details details) {
160        String violation = "";
161        if (shouldHaveLineBreakBefore(option, details)) {
162            violation = MSG_KEY_LINE_BREAK_BEFORE;
163        }
164        else if (shouldBeOnSameLine(option, details)) {
165            violation = MSG_KEY_LINE_SAME;
166        }
167        else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) {
168            violation = MSG_KEY_LINE_ALONE;
169        }
170        return violation;
171    }
172
173    /**
174     * Checks whether a right curly should have a line break before.
175     * @param bracePolicy option for placing the right curly brace.
176     * @param details details for validation.
177     * @return true if a right curly should have a line break before.
178     */
179    private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
180                                                     Details details) {
181        return bracePolicy == RightCurlyOption.SAME
182                && !hasLineBreakBefore(details.rcurly)
183                && details.lcurly.getLineNo() != details.rcurly.getLineNo();
184    }
185
186    /**
187     * Checks that a right curly should be on the same line as the next statement.
188     * @param bracePolicy option for placing the right curly brace
189     * @param details Details for validation
190     * @return true if a right curly should be alone on a line.
191     */
192    private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
193        return bracePolicy == RightCurlyOption.SAME
194                && !details.shouldCheckLastRcurly
195                && details.rcurly.getLineNo() != details.nextToken.getLineNo();
196    }
197
198    /**
199     * Checks that a right curly should be alone on a line.
200     * @param bracePolicy option for placing the right curly brace
201     * @param details Details for validation
202     * @param targetSrcLine A string with contents of rcurly's line
203     * @return true if a right curly should be alone on a line.
204     */
205    private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy,
206                                               Details details,
207                                               String targetSrcLine) {
208        return bracePolicy == RightCurlyOption.ALONE
209                    && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
210                || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
211                    && shouldBeAloneOnLineWithAloneOrSinglelineOption(details, targetSrcLine)
212                || details.shouldCheckLastRcurly
213                    && details.rcurly.getLineNo() == details.nextToken.getLineNo();
214    }
215
216    /**
217     * Whether right curly should be alone on line when ALONE option is used.
218     * @param details details for validation.
219     * @param targetSrcLine A string with contents of rcurly's line
220     * @return true, if right curly should be alone on line when ALONE option is used.
221     */
222    private static boolean shouldBeAloneOnLineWithAloneOption(Details details,
223                                                              String targetSrcLine) {
224        return !isAloneOnLine(details, targetSrcLine)
225                && !isEmptyBody(details.lcurly);
226    }
227
228    /**
229     * Whether right curly should be alone on line when ALONE_OR_SINGLELINE option is used.
230     * @param details details for validation.
231     * @param targetSrcLine A string with contents of rcurly's line
232     * @return true, if right curly should be alone on line
233     *         when ALONE_OR_SINGLELINE option is used.
234     */
235    private static boolean shouldBeAloneOnLineWithAloneOrSinglelineOption(Details details,
236                                                                          String targetSrcLine) {
237        return !isAloneOnLine(details, targetSrcLine)
238                && !isSingleLineBlock(details)
239                && !isEmptyBody(details.lcurly);
240    }
241
242    /**
243     * Checks whether right curly is alone on a line.
244     * @param details for validation.
245     * @param targetSrcLine A string with contents of rcurly's line
246     * @return true if right curly is alone on a line.
247     */
248    private static boolean isAloneOnLine(Details details, String targetSrcLine) {
249        final DetailAST rcurly = details.rcurly;
250        final DetailAST nextToken = details.nextToken;
251        return (rcurly.getLineNo() != nextToken.getLineNo() || skipDoubleBraceInstInit(details))
252                && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSrcLine);
253    }
254
255    /**
256     * This method determines if the double brace initialization should be skipped over by the
257     * check. Double brace initializations are treated differently. The corresponding inner
258     * rcurly is treated as if it was alone on line even when it may be followed by another
259     * rcurly and a semi, raising no violations.
260     * <i>Please do note though that the line should not contain anything other than the following
261     * right curly and the semi following it or else violations will be raised.</i>
262     * Only the kind of double brace initializations shown in the following example code will be
263     * skipped over:<br>
264     * <pre>
265     *     {@code Map<String, String> map = new LinkedHashMap<>() {{
266     *           put("alpha", "man");
267     *       }}; // no violation}
268     * </pre>
269     *
270     * @param details {@link Details} object containing the details relevant to the rcurly
271     * @return if the double brace initialization rcurly should be skipped over by the check
272     */
273    private static boolean skipDoubleBraceInstInit(Details details) {
274        final DetailAST rcurly = details.rcurly;
275        final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken);
276        return rcurly.getParent().getParent().getType() == TokenTypes.INSTANCE_INIT
277                && details.nextToken.getType() == TokenTypes.RCURLY
278                && tokenAfterNextToken.getType() == TokenTypes.SEMI
279                && rcurly.getLineNo() != Details.getNextToken(tokenAfterNextToken).getLineNo();
280
281    }
282
283    /**
284     * Checks whether block has a single-line format.
285     * @param details for validation.
286     * @return true if block has single-line format.
287     */
288    private static boolean isSingleLineBlock(Details details) {
289        final DetailAST rcurly = details.rcurly;
290        final DetailAST lcurly = details.lcurly;
291        DetailAST nextToken = details.nextToken;
292        while (nextToken.getType() == TokenTypes.LITERAL_ELSE) {
293            nextToken = Details.getNextToken(nextToken);
294        }
295        if (nextToken.getType() == TokenTypes.DO_WHILE) {
296            final DetailAST doWhileSemi = nextToken.getParent().getLastChild();
297            nextToken = Details.getNextToken(doWhileSemi);
298        }
299        return rcurly.getLineNo() == lcurly.getLineNo()
300                && rcurly.getLineNo() != nextToken.getLineNo();
301    }
302
303    /**
304     * Checks if definition body is empty.
305     * @param lcurly left curly.
306     * @return true if definition body is empty.
307     */
308    private static boolean isEmptyBody(DetailAST lcurly) {
309        boolean result = false;
310        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
311            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
312                result = true;
313            }
314        }
315        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
316            result = true;
317        }
318        return result;
319    }
320
321    /**
322     * Checks if right curly has line break before.
323     * @param rightCurly right curly token.
324     * @return true, if right curly has line break before.
325     */
326    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
327        final DetailAST previousToken = rightCurly.getPreviousSibling();
328        return previousToken == null
329                || rightCurly.getLineNo() != previousToken.getLineNo();
330    }
331
332    /**
333     * Structure that contains all details for validation.
334     */
335    private static final class Details {
336
337        /** Right curly. */
338        private final DetailAST rcurly;
339        /** Left curly. */
340        private final DetailAST lcurly;
341        /** Next token. */
342        private final DetailAST nextToken;
343        /** Should check last right curly. */
344        private final boolean shouldCheckLastRcurly;
345
346        /**
347         * Constructor.
348         * @param lcurly the lcurly of the token whose details are being collected
349         * @param rcurly the rcurly of the token whose details are being collected
350         * @param nextToken the token after the token whose details are being collected
351         * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
352         */
353        private Details(DetailAST lcurly, DetailAST rcurly,
354                        DetailAST nextToken, boolean shouldCheckLastRcurly) {
355            this.lcurly = lcurly;
356            this.rcurly = rcurly;
357            this.nextToken = nextToken;
358            this.shouldCheckLastRcurly = shouldCheckLastRcurly;
359        }
360
361        /**
362         * Collects validation Details.
363         * @param ast a {@code DetailAST} value
364         * @return object containing all details to make a validation
365         */
366        private static Details getDetails(DetailAST ast) {
367            final Details details;
368            switch (ast.getType()) {
369                case TokenTypes.LITERAL_TRY:
370                case TokenTypes.LITERAL_CATCH:
371                case TokenTypes.LITERAL_FINALLY:
372                    details = getDetailsForTryCatchFinally(ast);
373                    break;
374                case TokenTypes.LITERAL_IF:
375                case TokenTypes.LITERAL_ELSE:
376                    details = getDetailsForIfElse(ast);
377                    break;
378                case TokenTypes.LITERAL_DO:
379                case TokenTypes.LITERAL_WHILE:
380                case TokenTypes.LITERAL_FOR:
381                    details = getDetailsForLoops(ast);
382                    break;
383                default:
384                    details = getDetailsForOthers(ast);
385                    break;
386            }
387            return details;
388        }
389
390        /**
391         * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
392         * @param ast a {@code DetailAST} value
393         * @return object containing all details to make a validation
394         */
395        private static Details getDetailsForTryCatchFinally(DetailAST ast) {
396            boolean shouldCheckLastRcurly = false;
397            final DetailAST rcurly;
398            final DetailAST lcurly;
399            DetailAST nextToken;
400            final int tokenType = ast.getType();
401            if (tokenType == TokenTypes.LITERAL_TRY) {
402                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
403                    lcurly = ast.getFirstChild().getNextSibling();
404                }
405                else {
406                    lcurly = ast.getFirstChild();
407                }
408                nextToken = lcurly.getNextSibling();
409                rcurly = lcurly.getLastChild();
410
411                if (nextToken == null) {
412                    shouldCheckLastRcurly = true;
413                    nextToken = getNextToken(ast);
414                }
415            }
416            else if (tokenType == TokenTypes.LITERAL_CATCH) {
417                nextToken = ast.getNextSibling();
418                lcurly = ast.getLastChild();
419                rcurly = lcurly.getLastChild();
420                if (nextToken == null) {
421                    shouldCheckLastRcurly = true;
422                    nextToken = getNextToken(ast);
423                }
424            }
425            else {
426                shouldCheckLastRcurly = true;
427                nextToken = getNextToken(ast);
428                lcurly = ast.getFirstChild();
429                rcurly = lcurly.getLastChild();
430            }
431            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
432        }
433
434        /**
435         * Collects validation details for LITERAL_IF and LITERAL_ELSE.
436         * @param ast a {@code DetailAST} value
437         * @return object containing all details to make a validation
438         */
439        private static Details getDetailsForIfElse(DetailAST ast) {
440            boolean shouldCheckLastRcurly = false;
441            final DetailAST lcurly;
442            DetailAST nextToken;
443            final int tokenType = ast.getType();
444            if (tokenType == TokenTypes.LITERAL_IF) {
445                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
446                if (nextToken == null) {
447                    shouldCheckLastRcurly = true;
448                    nextToken = getNextToken(ast);
449                    lcurly = ast.getLastChild();
450                }
451                else {
452                    lcurly = nextToken.getPreviousSibling();
453                }
454            }
455            else {
456                shouldCheckLastRcurly = true;
457                nextToken = getNextToken(ast);
458                lcurly = ast.getFirstChild();
459            }
460            DetailAST rcurly = null;
461            if (lcurly.getType() == TokenTypes.SLIST) {
462                rcurly = lcurly.getLastChild();
463            }
464            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
465        }
466
467        /**
468         * Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, and
469         * INSTANCE_INIT.
470         * @param ast a {@code DetailAST} value
471         * @return an object containing all details to make a validation
472         */
473        private static Details getDetailsForOthers(DetailAST ast) {
474            DetailAST rcurly = null;
475            final DetailAST lcurly;
476            final int tokenType = ast.getType();
477            if (tokenType == TokenTypes.CLASS_DEF) {
478                final DetailAST child = ast.getLastChild();
479                lcurly = child.getFirstChild();
480                rcurly = child.getLastChild();
481            }
482            else {
483                lcurly = ast.findFirstToken(TokenTypes.SLIST);
484                if (lcurly != null) {
485                    // SLIST could be absent if method is abstract
486                    rcurly = lcurly.getLastChild();
487                }
488            }
489            return new Details(lcurly, rcurly, getNextToken(ast), false);
490        }
491
492        /**
493         * Collects validation details for loops' tokens.
494         * @param ast a {@code DetailAST} value
495         * @return an object containing all details to make a validation
496         */
497        private static Details getDetailsForLoops(DetailAST ast) {
498            DetailAST rcurly = null;
499            final DetailAST lcurly;
500            final DetailAST nextToken;
501            final int tokenType = ast.getType();
502            if (tokenType == TokenTypes.LITERAL_DO) {
503                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
504                lcurly = ast.findFirstToken(TokenTypes.SLIST);
505                if (lcurly != null) {
506                    rcurly = lcurly.getLastChild();
507                }
508            }
509            else {
510                lcurly = ast.findFirstToken(TokenTypes.SLIST);
511                if (lcurly != null) {
512                    // SLIST could be absent in code like "while(true);"
513                    rcurly = lcurly.getLastChild();
514                }
515                nextToken = getNextToken(ast);
516            }
517            return new Details(lcurly, rcurly, nextToken, false);
518        }
519
520        /**
521         * Finds next token after the given one.
522         * @param ast the given node.
523         * @return the token which represents next lexical item.
524         */
525        private static DetailAST getNextToken(DetailAST ast) {
526            DetailAST next = null;
527            DetailAST parent = ast;
528            while (next == null && parent != null) {
529                next = parent.getNextSibling();
530                parent = parent.getParent();
531            }
532            if (next == null) {
533                // a DetailAST object with DetailAST#NOT_INITIALIZED for line and column numbers
534                // that no 'actual' DetailAST objects can have.
535                next = new DetailAST();
536            }
537            else {
538                next = CheckUtil.getFirstNode(next);
539            }
540            return next;
541        }
542
543    }
544
545}