001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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 && rcurly.getType() == TokenTypes.RCURLY) {
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    private static Details getDetails(DetailAST ast) {
314        // Attempt to locate the tokens to do the check
315        boolean shouldCheckLastRcurly = false;
316        DetailAST rcurly = null;
317        final DetailAST lcurly;
318        DetailAST nextToken;
319
320        switch (ast.getType()) {
321            case TokenTypes.LITERAL_TRY:
322                lcurly = ast.getFirstChild();
323                nextToken = lcurly.getNextSibling();
324                rcurly = lcurly.getLastChild();
325                break;
326            case TokenTypes.LITERAL_CATCH:
327                nextToken = ast.getNextSibling();
328                lcurly = ast.getLastChild();
329                rcurly = lcurly.getLastChild();
330                if (nextToken == null) {
331                    shouldCheckLastRcurly = true;
332                    nextToken = getNextToken(ast);
333                }
334                break;
335            case TokenTypes.LITERAL_IF:
336                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
337                if (nextToken == null) {
338                    shouldCheckLastRcurly = true;
339                    nextToken = getNextToken(ast);
340                    lcurly = ast.getLastChild();
341                    rcurly = lcurly.getLastChild();
342                }
343                else {
344                    lcurly = nextToken.getPreviousSibling();
345                    rcurly = lcurly.getLastChild();
346                }
347                break;
348            case TokenTypes.LITERAL_ELSE:
349            case TokenTypes.LITERAL_FINALLY:
350                shouldCheckLastRcurly = true;
351                nextToken = getNextToken(ast);
352                lcurly = ast.getFirstChild();
353                rcurly = lcurly.getLastChild();
354                break;
355            case TokenTypes.CLASS_DEF:
356                final DetailAST child = ast.getLastChild();
357                lcurly = child.getFirstChild();
358                rcurly = child.getLastChild();
359                nextToken = ast;
360                break;
361            case TokenTypes.CTOR_DEF:
362            case TokenTypes.STATIC_INIT:
363            case TokenTypes.INSTANCE_INIT:
364                lcurly = ast.findFirstToken(TokenTypes.SLIST);
365                rcurly = lcurly.getLastChild();
366                nextToken = getNextToken(ast);
367                break;
368            case TokenTypes.LITERAL_DO:
369                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
370                lcurly = ast.findFirstToken(TokenTypes.SLIST);
371                rcurly = lcurly.getLastChild();
372                break;
373            default:
374                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
375                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, only.
376                // It has been done to improve coverage to 100%. I couldn't replace it with
377                // if-else-if block because code was ugly and didn't pass pmd check.
378
379                lcurly = ast.findFirstToken(TokenTypes.SLIST);
380                if (lcurly != null) {
381                    // SLIST could be absent if method is abstract,
382                    // and code like "while(true);"
383                    rcurly = lcurly.getLastChild();
384                }
385                nextToken = getNextToken(ast);
386                break;
387        }
388
389        final Details details = new Details();
390        details.rcurly = rcurly;
391        details.lcurly = lcurly;
392        details.nextToken = nextToken;
393        details.shouldCheckLastRcurly = shouldCheckLastRcurly;
394
395        return details;
396    }
397
398    /**
399     * Checks if definition body is empty.
400     * @param lcurly left curly.
401     * @return true if definition body is empty.
402     */
403    private static boolean isEmptyBody(DetailAST lcurly) {
404        boolean result = false;
405        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
406            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
407                result = true;
408            }
409        }
410        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
411            result = true;
412        }
413        return result;
414    }
415
416    /**
417     * Finds next token after the given one.
418     * @param ast the given node.
419     * @return the token which represents next lexical item.
420     */
421    private static DetailAST getNextToken(DetailAST ast) {
422        DetailAST next = null;
423        DetailAST parent = ast;
424        while (next == null) {
425            next = parent.getNextSibling();
426            parent = parent.getParent();
427        }
428        return CheckUtils.getFirstNode(next);
429    }
430
431    /**
432     * Checks if right curly has line break before.
433     * @param rightCurly right curly token.
434     * @return true, if right curly has line break before.
435     */
436    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
437        final DetailAST previousToken = rightCurly.getPreviousSibling();
438        return previousToken == null
439                || rightCurly.getLineNo() != previousToken.getLineNo();
440    }
441
442    /**
443     * Structure that contains all details for validation.
444     */
445    private static class Details {
446        /** Right curly. */
447        private DetailAST rcurly;
448        /** Left curly. */
449        private DetailAST lcurly;
450        /** Next token. */
451        private DetailAST nextToken;
452        /** Should check last right curly. */
453        private boolean shouldCheckLastRcurly;
454    }
455}