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 = 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     * Collects validation details.
324     * @param ast detail ast.
325     * @return object that contain all details to make a validation.
326     * @noinspection SwitchStatementDensity
327     */
328    // -@cs[JavaNCSS|ExecutableStatementCount|CyclomaticComplexity|NPathComplexity] getDetails()
329    // method is a huge SWITCH, it has to be monolithic
330    private static Details getDetails(DetailAST ast) {
331        // Attempt to locate the tokens to do the check
332        boolean shouldCheckLastRcurly = false;
333        DetailAST rcurly = null;
334        final DetailAST lcurly;
335        DetailAST nextToken;
336
337        switch (ast.getType()) {
338            case TokenTypes.LITERAL_TRY:
339                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
340                    lcurly = ast.getFirstChild().getNextSibling();
341                }
342                else {
343                    lcurly = ast.getFirstChild();
344                }
345                nextToken = lcurly.getNextSibling();
346                rcurly = lcurly.getLastChild();
347
348                if (nextToken == null) {
349                    shouldCheckLastRcurly = true;
350                    nextToken = getNextToken(ast);
351                }
352                break;
353            case TokenTypes.LITERAL_CATCH:
354                nextToken = ast.getNextSibling();
355                lcurly = ast.getLastChild();
356                rcurly = lcurly.getLastChild();
357                if (nextToken == null) {
358                    shouldCheckLastRcurly = true;
359                    nextToken = getNextToken(ast);
360                }
361                break;
362            case TokenTypes.LITERAL_IF:
363                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
364                if (nextToken == null) {
365                    shouldCheckLastRcurly = true;
366                    nextToken = getNextToken(ast);
367                    lcurly = ast.getLastChild();
368                }
369                else {
370                    lcurly = nextToken.getPreviousSibling();
371                }
372                if (lcurly.getType() == TokenTypes.SLIST) {
373                    rcurly = lcurly.getLastChild();
374                }
375                break;
376            case TokenTypes.LITERAL_ELSE:
377            case TokenTypes.LITERAL_FINALLY:
378                shouldCheckLastRcurly = true;
379                nextToken = getNextToken(ast);
380                lcurly = ast.getFirstChild();
381                if (lcurly.getType() == TokenTypes.SLIST) {
382                    rcurly = lcurly.getLastChild();
383                }
384                break;
385            case TokenTypes.CLASS_DEF:
386                final DetailAST child = ast.getLastChild();
387                lcurly = child.getFirstChild();
388                rcurly = child.getLastChild();
389                nextToken = ast;
390                break;
391            case TokenTypes.CTOR_DEF:
392            case TokenTypes.STATIC_INIT:
393            case TokenTypes.INSTANCE_INIT:
394                lcurly = ast.findFirstToken(TokenTypes.SLIST);
395                rcurly = lcurly.getLastChild();
396                nextToken = getNextToken(ast);
397                break;
398            case TokenTypes.LITERAL_DO:
399                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
400                lcurly = ast.findFirstToken(TokenTypes.SLIST);
401                if (lcurly != null) {
402                    rcurly = lcurly.getLastChild();
403                }
404                break;
405            case TokenTypes.LAMBDA:
406                lcurly = ast.findFirstToken(TokenTypes.SLIST);
407                nextToken = getNextToken(ast);
408                if (nextToken.getType() != TokenTypes.RPAREN
409                        && nextToken.getType() != TokenTypes.COMMA) {
410                    shouldCheckLastRcurly = true;
411                    nextToken = getNextToken(nextToken);
412                }
413                if (lcurly != null) {
414                    rcurly = lcurly.getLastChild();
415                }
416                break;
417            default:
418                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
419                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE only.
420                // It has been done to improve coverage to 100%. I couldn't replace it with
421                // if-else-if block because code was ugly and didn't pass pmd check.
422
423                lcurly = ast.findFirstToken(TokenTypes.SLIST);
424                if (lcurly != null) {
425                    // SLIST could be absent if method is abstract,
426                    // and code like "while(true);"
427                    rcurly = lcurly.getLastChild();
428                }
429                nextToken = getNextToken(ast);
430                break;
431        }
432
433        final Details details = new Details();
434        details.rcurly = rcurly;
435        details.lcurly = lcurly;
436        details.nextToken = nextToken;
437        details.shouldCheckLastRcurly = shouldCheckLastRcurly;
438
439        return details;
440    }
441
442    /**
443     * Checks if definition body is empty.
444     * @param lcurly left curly.
445     * @return true if definition body is empty.
446     */
447    private static boolean isEmptyBody(DetailAST lcurly) {
448        boolean result = false;
449        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
450            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
451                result = true;
452            }
453        }
454        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
455            result = true;
456        }
457        return result;
458    }
459
460    /**
461     * Finds next token after the given one.
462     * @param ast the given node.
463     * @return the token which represents next lexical item.
464     */
465    private static DetailAST getNextToken(DetailAST ast) {
466        DetailAST next = null;
467        DetailAST parent = ast;
468        while (next == null) {
469            next = parent.getNextSibling();
470            parent = parent.getParent();
471        }
472        return CheckUtils.getFirstNode(next);
473    }
474
475    /**
476     * Checks if right curly has line break before.
477     * @param rightCurly right curly token.
478     * @return true, if right curly has line break before.
479     */
480    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
481        final DetailAST previousToken = rightCurly.getPreviousSibling();
482        return previousToken == null
483                || rightCurly.getLineNo() != previousToken.getLineNo();
484    }
485
486    /**
487     * Structure that contains all details for validation.
488     */
489    private static class Details {
490        /** Right curly. */
491        private DetailAST rcurly;
492        /** Left curly. */
493        private DetailAST lcurly;
494        /** Next token. */
495        private DetailAST nextToken;
496        /** Should check last right curly. */
497        private boolean shouldCheckLastRcurly;
498    }
499}