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 org.apache.commons.beanutils.ConversionException;
025
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.Scope;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
032import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
033
034/**
035 * <p>
036 * Checks the placement of right curly braces.
037 * The policy to verify is specified using the {@link RightCurlyOption} class
038 * and defaults to {@link RightCurlyOption#SAME}.
039 * </p>
040 * <p> By default the check will check the following tokens:
041 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
042 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
043 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
044 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
045 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
046 * Other acceptable tokens are:
047 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
048 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
049 *  {@link TokenTypes#CTOR_DEF CTOR_DEF}.
050 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
051 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
052 *  {@link TokenTypes#LITERAL_DO LITERAL_DO}.
053 *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
054 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
055 * </p>
056 * <p>
057 * <b>shouldStartLine</b> - does the check need to check
058 * if right curly starts line. Default value is <b>true</b>
059 * </p>
060 * <p>
061 * An example of how to configure the check is:
062 * </p>
063 * <pre>
064 * &lt;module name="RightCurly"/&gt;
065 * </pre>
066 * <p>
067 * An example of how to configure the check with policy
068 * {@link RightCurlyOption#ALONE} for {@code else} and
069 * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is:
070 * </p>
071 * <pre>
072 * &lt;module name="RightCurly"&gt;
073 *     &lt;property name="tokens" value="LITERAL_ELSE"/&gt;
074 *     &lt;property name="option" value="alone"/&gt;
075 * &lt;/module&gt;
076 * </pre>
077 *
078 * @author Oliver Burn
079 * @author lkuehne
080 * @author o_sukhodolsky
081 * @author maxvetrenko
082 * @author Andrei Selkin
083 * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
084 */
085public class RightCurlyCheck extends AbstractCheck {
086    /**
087     * A key is pointing to the warning message text in "messages.properties"
088     * file.
089     */
090    public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
091
092    /**
093     * A key is pointing to the warning message text in "messages.properties"
094     * file.
095     */
096    public static final String MSG_KEY_LINE_ALONE = "line.alone";
097
098    /**
099     * A key is pointing to the warning message text in "messages.properties"
100     * file.
101     */
102    public static final String MSG_KEY_LINE_SAME = "line.same";
103
104    /**
105     * A key is pointing to the warning message text in "messages.properties"
106     * file.
107     */
108    public static final String MSG_KEY_LINE_NEW = "line.new";
109
110    /** Do we need to check if right curly starts line. */
111    private boolean shouldStartLine = true;
112
113    /** The policy to enforce. */
114    private RightCurlyOption option = RightCurlyOption.SAME;
115
116    /**
117     * Set the option to enforce.
118     * @param optionStr string to decode option from
119     * @throws ConversionException if unable to decode
120     */
121    public void setOption(String optionStr) {
122        try {
123            option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
124        }
125        catch (IllegalArgumentException iae) {
126            throw new ConversionException("unable to parse " + optionStr, iae);
127        }
128    }
129
130    /**
131     * Does the check need to check if right curly starts line.
132     * @param flag new value of this property.
133     */
134    public void setShouldStartLine(boolean flag) {
135        shouldStartLine = flag;
136    }
137
138    @Override
139    public int[] getDefaultTokens() {
140        return new int[] {
141            TokenTypes.LITERAL_TRY,
142            TokenTypes.LITERAL_CATCH,
143            TokenTypes.LITERAL_FINALLY,
144            TokenTypes.LITERAL_IF,
145            TokenTypes.LITERAL_ELSE,
146        };
147    }
148
149    @Override
150    public int[] getAcceptableTokens() {
151        return new int[] {
152            TokenTypes.LITERAL_TRY,
153            TokenTypes.LITERAL_CATCH,
154            TokenTypes.LITERAL_FINALLY,
155            TokenTypes.LITERAL_IF,
156            TokenTypes.LITERAL_ELSE,
157            TokenTypes.CLASS_DEF,
158            TokenTypes.METHOD_DEF,
159            TokenTypes.CTOR_DEF,
160            TokenTypes.LITERAL_FOR,
161            TokenTypes.LITERAL_WHILE,
162            TokenTypes.LITERAL_DO,
163            TokenTypes.STATIC_INIT,
164            TokenTypes.INSTANCE_INIT,
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;
180            if (shouldStartLine) {
181                final String targetSourceLine = getLines()[rcurly.getLineNo() - 1];
182                violation = validate(details, option, true, targetSourceLine);
183            }
184            else {
185                violation = validate(details, option, false, "");
186            }
187
188            if (!violation.isEmpty()) {
189                log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
190            }
191        }
192    }
193
194    /**
195     * Does general validation.
196     * @param details for validation.
197     * @param bracePolicy for placing the right curly brace.
198     * @param shouldStartLine do we need to check if right curly starts line.
199     * @param targetSourceLine line that we need to check if shouldStartLine is true.
200     * @return violation message or empty string
201     *     if there was not violation during validation.
202     */
203    private static String validate(Details details, RightCurlyOption bracePolicy,
204                                   boolean shouldStartLine, String targetSourceLine) {
205        final DetailAST rcurly = details.rcurly;
206        final DetailAST lcurly = details.lcurly;
207        final DetailAST nextToken = details.nextToken;
208        final boolean shouldCheckLastRcurly = details.shouldCheckLastRcurly;
209        String violation = "";
210
211        if (bracePolicy == RightCurlyOption.SAME
212                && !hasLineBreakBefore(rcurly)
213                && lcurly.getLineNo() != rcurly.getLineNo()) {
214            violation = MSG_KEY_LINE_BREAK_BEFORE;
215        }
216        else if (shouldCheckLastRcurly) {
217            if (rcurly.getLineNo() == nextToken.getLineNo()) {
218                violation = MSG_KEY_LINE_ALONE;
219            }
220        }
221        else if (shouldBeOnSameLine(bracePolicy, details)) {
222            violation = MSG_KEY_LINE_SAME;
223        }
224        else if (shouldBeAloneOnLine(bracePolicy, details)) {
225            violation = MSG_KEY_LINE_ALONE;
226        }
227        else if (shouldStartLine && !isOnStartOfLine(details, targetSourceLine)) {
228            violation = MSG_KEY_LINE_NEW;
229        }
230        return violation;
231    }
232
233    /**
234     * Checks that a right curly should be on the same line as the next statement.
235     * @param bracePolicy option for placing the right curly brace
236     * @param details Details for validation
237     * @return true if a right curly should be alone on a line.
238     */
239    private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
240        return bracePolicy == RightCurlyOption.SAME
241                && details.rcurly.getLineNo() != details.nextToken.getLineNo();
242    }
243
244    /**
245     * Checks that a right curly should be alone on a line.
246     * @param bracePolicy option for placing the right curly brace
247     * @param details Details for validation
248     * @return true if a right curly should be alone on a line.
249     */
250    private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) {
251        return bracePolicy == RightCurlyOption.ALONE
252                && !isAloneOnLine(details)
253                && !isEmptyBody(details.lcurly)
254                || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
255                && !isAloneOnLine(details)
256                && !isSingleLineBlock(details)
257                && !isAnonInnerClassInit(details.lcurly)
258                && !isEmptyBody(details.lcurly);
259    }
260
261    /**
262     * Whether right curly brace starts target source line.
263     * @param details Details of right curly brace for validation
264     * @param targetSourceLine source line to check
265     * @return true if right curly brace starts target source line.
266     */
267    private static boolean isOnStartOfLine(Details details, String targetSourceLine) {
268        return CommonUtils.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine)
269                || details.lcurly.getLineNo() == details.rcurly.getLineNo();
270    }
271
272    /**
273     * Checks whether right curly is alone on a line.
274     * @param details for validation.
275     * @return true if right curly is alone on a line.
276     */
277    private static boolean isAloneOnLine(Details details) {
278        final DetailAST rcurly = details.rcurly;
279        final DetailAST lcurly = details.lcurly;
280        final DetailAST nextToken = details.nextToken;
281        return rcurly.getLineNo() != lcurly.getLineNo()
282            && rcurly.getLineNo() != nextToken.getLineNo();
283    }
284
285    /**
286     * Checks whether block has a single-line format.
287     * @param details for validation.
288     * @return true if block has single-line format.
289     */
290    private static boolean isSingleLineBlock(Details details) {
291        final DetailAST rcurly = details.rcurly;
292        final DetailAST lcurly = details.lcurly;
293        final DetailAST nextToken = details.nextToken;
294        return rcurly.getLineNo() == lcurly.getLineNo()
295            && rcurly.getLineNo() != nextToken.getLineNo();
296    }
297
298    /**
299     * Checks whether lcurly is in anonymous inner class initialization.
300     * @param lcurly left curly token.
301     * @return true if lcurly begins anonymous inner class initialization.
302     */
303    private static boolean isAnonInnerClassInit(DetailAST lcurly) {
304        final Scope surroundingScope = ScopeUtils.getSurroundingScope(lcurly);
305        return surroundingScope.ordinal() == Scope.ANONINNER.ordinal();
306    }
307
308    /**
309     * Collects validation details.
310     * @param ast detail ast.
311     * @return object that contain all details to make a validation.
312     */
313    // -@cs[JavaNCSS] getDetails() method is a huge SWITCH, it has to be monolithic
314    // -@cs[ExecutableStatementCount] getDetails() method is a huge SWITCH, it has to be monolithic
315    // -@cs[NPathComplexity] getDetails() method is a huge SWITCH, it has to be monolithic
316    private static Details getDetails(DetailAST ast) {
317        // Attempt to locate the tokens to do the check
318        boolean shouldCheckLastRcurly = false;
319        DetailAST rcurly = null;
320        final DetailAST lcurly;
321        DetailAST nextToken;
322
323        switch (ast.getType()) {
324            case TokenTypes.LITERAL_TRY:
325                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
326                    lcurly = ast.getFirstChild().getNextSibling();
327                }
328                else {
329                    lcurly = ast.getFirstChild();
330                }
331                nextToken = lcurly.getNextSibling();
332                rcurly = lcurly.getLastChild();
333
334                if (nextToken == null) {
335                    shouldCheckLastRcurly = true;
336                    nextToken = getNextToken(ast);
337                }
338                break;
339            case TokenTypes.LITERAL_CATCH:
340                nextToken = ast.getNextSibling();
341                lcurly = ast.getLastChild();
342                rcurly = lcurly.getLastChild();
343                if (nextToken == null) {
344                    shouldCheckLastRcurly = true;
345                    nextToken = getNextToken(ast);
346                }
347                break;
348            case TokenTypes.LITERAL_IF:
349                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
350                if (nextToken == null) {
351                    shouldCheckLastRcurly = true;
352                    nextToken = getNextToken(ast);
353                    lcurly = ast.getLastChild();
354                }
355                else {
356                    lcurly = nextToken.getPreviousSibling();
357                }
358                if (lcurly.getType() == TokenTypes.SLIST) {
359                    rcurly = lcurly.getLastChild();
360                }
361                break;
362            case TokenTypes.LITERAL_ELSE:
363            case TokenTypes.LITERAL_FINALLY:
364                shouldCheckLastRcurly = true;
365                nextToken = getNextToken(ast);
366                lcurly = ast.getFirstChild();
367                if (lcurly.getType() == TokenTypes.SLIST) {
368                    rcurly = lcurly.getLastChild();
369                }
370                break;
371            case TokenTypes.CLASS_DEF:
372                final DetailAST child = ast.getLastChild();
373                lcurly = child.getFirstChild();
374                rcurly = child.getLastChild();
375                nextToken = ast;
376                break;
377            case TokenTypes.CTOR_DEF:
378            case TokenTypes.STATIC_INIT:
379            case TokenTypes.INSTANCE_INIT:
380                lcurly = ast.findFirstToken(TokenTypes.SLIST);
381                rcurly = lcurly.getLastChild();
382                nextToken = getNextToken(ast);
383                break;
384            case TokenTypes.LITERAL_DO:
385                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
386                lcurly = ast.findFirstToken(TokenTypes.SLIST);
387                if (lcurly != null) {
388                    rcurly = lcurly.getLastChild();
389                }
390                break;
391            default:
392                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
393                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, only.
394                // It has been done to improve coverage to 100%. I couldn't replace it with
395                // if-else-if block because code was ugly and didn't pass pmd check.
396
397                lcurly = ast.findFirstToken(TokenTypes.SLIST);
398                if (lcurly != null) {
399                    // SLIST could be absent if method is abstract,
400                    // and code like "while(true);"
401                    rcurly = lcurly.getLastChild();
402                }
403                nextToken = getNextToken(ast);
404                break;
405        }
406
407        final Details details = new Details();
408        details.rcurly = rcurly;
409        details.lcurly = lcurly;
410        details.nextToken = nextToken;
411        details.shouldCheckLastRcurly = shouldCheckLastRcurly;
412
413        return details;
414    }
415
416    /**
417     * Checks if definition body is empty.
418     * @param lcurly left curly.
419     * @return true if definition body is empty.
420     */
421    private static boolean isEmptyBody(DetailAST lcurly) {
422        boolean result = false;
423        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
424            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
425                result = true;
426            }
427        }
428        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
429            result = true;
430        }
431        return result;
432    }
433
434    /**
435     * Finds next token after the given one.
436     * @param ast the given node.
437     * @return the token which represents next lexical item.
438     */
439    private static DetailAST getNextToken(DetailAST ast) {
440        DetailAST next = null;
441        DetailAST parent = ast;
442        while (next == null) {
443            next = parent.getNextSibling();
444            parent = parent.getParent();
445        }
446        return CheckUtils.getFirstNode(next);
447    }
448
449    /**
450     * Checks if right curly has line break before.
451     * @param rightCurly right curly token.
452     * @return true, if right curly has line break before.
453     */
454    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
455        final DetailAST previousToken = rightCurly.getPreviousSibling();
456        return previousToken == null
457                || rightCurly.getLineNo() != previousToken.getLineNo();
458    }
459
460    /**
461     * Structure that contains all details for validation.
462     */
463    private static class Details {
464        /** Right curly. */
465        private DetailAST rcurly;
466        /** Left curly. */
467        private DetailAST lcurly;
468        /** Next token. */
469        private DetailAST nextToken;
470        /** Should check last right curly. */
471        private boolean shouldCheckLastRcurly;
472    }
473}