001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.blocks;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023import com.puppycrawl.tools.checkstyle.api.Utils;
024import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
025import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
026
027/**
028 * <p>
029 * Checks the placement of right curly braces.
030 * The policy to verify is specified using the {@link RightCurlyOption} class
031 * and defaults to {@link RightCurlyOption#SAME}.
032 * </p>
033 * <p> By default the check will check the following tokens:
034 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
035 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
036 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
037 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
038 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
039 * Other acceptable tokens are:
040 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
041 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
042 *  {@link TokenTypes#CTOR_DEF CTOR_DEF}.
043 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
044 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
045 *  {@link TokenTypes#LITERAL_DO LITERAL_DO}.
046 *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
047 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
048 * </p>
049 * <p>
050 * An example of how to configure the check is:
051 * </p>
052 * <pre>
053 * &lt;module name="RightCurly"/&gt;
054 * </pre>
055 * <p>
056 * An example of how to configure the check with policy
057 * {@link RightCurlyOption#ALONE} for <code>else</code> and
058 * <code>{@link TokenTypes#METHOD_DEF METHOD_DEF}</code>tokens is:
059 * </p>
060 * <pre>
061 * &lt;module name="RightCurly"&gt;
062 *     &lt;property name="tokens" value="LITERAL_ELSE"/&gt;
063 *     &lt;property name="option" value="alone"/&gt;
064 * &lt;/module&gt;
065 * </pre>
066 *
067 * @author Oliver Burn
068 * @author lkuehne
069 * @author o_sukhodolsky
070 * @author maxvetrenko
071 * @version 2.0
072 */
073public class RightCurlyCheck extends AbstractOptionCheck<RightCurlyOption>
074{
075    /** Do we need to check if rculry starts line. */
076    private boolean shouldStartLine = true;
077
078    /**
079     * A key is pointing to the warning message text in "messages.properties"
080     * file.
081     */
082    public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
083
084    /**
085     * A key is pointing to the warning message text in "messages.properties"
086     * file.
087     */
088    public static final String MSG_KEY_LINE_ALONE = "line.alone";
089
090    /**
091     * A key is pointing to the warning message text in "messages.properties"
092     * file.
093     */
094    public static final String MSG_KEY_LINE_SAME = "line.same";
095
096    /**
097     * A key is pointing to the warning message text in "messages.properties"
098     * file.
099     */
100    public static final String MSG_KEY_LINE_NEW = "line.new";
101
102    /**
103     * Sets the right curly option to same.
104     */
105    public RightCurlyCheck()
106    {
107        super(RightCurlyOption.SAME, RightCurlyOption.class);
108    }
109
110    /**
111     * Does the check need to check if rcurly starts line.
112     * @param flag new value of this property.
113     */
114    public void setShouldStartLine(boolean flag)
115    {
116        shouldStartLine = flag;
117    }
118
119    @Override
120    public int[] getDefaultTokens()
121    {
122        return new int[] {
123            TokenTypes.LITERAL_TRY,
124            TokenTypes.LITERAL_CATCH,
125            TokenTypes.LITERAL_FINALLY,
126            TokenTypes.LITERAL_IF,
127            TokenTypes.LITERAL_ELSE,
128        };
129    }
130
131    @Override
132    public int[] getAcceptableTokens()
133    {
134        return new int[] {
135            TokenTypes.LITERAL_TRY,
136            TokenTypes.LITERAL_CATCH,
137            TokenTypes.LITERAL_FINALLY,
138            TokenTypes.LITERAL_IF,
139            TokenTypes.LITERAL_ELSE,
140            TokenTypes.CLASS_DEF,
141            TokenTypes.METHOD_DEF,
142            TokenTypes.CTOR_DEF,
143            TokenTypes.LITERAL_FOR,
144            TokenTypes.LITERAL_WHILE,
145            TokenTypes.LITERAL_DO,
146            TokenTypes.STATIC_INIT,
147            TokenTypes.INSTANCE_INIT,
148        };
149    }
150
151    @Override
152    public void visitToken(DetailAST ast)
153    {
154        // Attempt to locate the tokens to do the check
155        DetailAST rcurly;
156        DetailAST lcurly;
157        DetailAST nextToken;
158        boolean shouldCheckLastRcurly = false;
159
160        switch (ast.getType()) {
161            case TokenTypes.LITERAL_TRY:
162                lcurly = ast.getFirstChild();
163                nextToken = lcurly.getNextSibling();
164                rcurly = lcurly.getLastChild();
165                break;
166            case TokenTypes.LITERAL_CATCH:
167                nextToken = ast.getNextSibling();
168                lcurly = ast.getLastChild();
169                rcurly = lcurly.getLastChild();
170                if (nextToken == null) {
171                    shouldCheckLastRcurly = true;
172                    nextToken = getNextToken(ast);
173                }
174                break;
175            case TokenTypes.LITERAL_IF:
176                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
177                if (nextToken != null) {
178                    lcurly = nextToken.getPreviousSibling();
179                    rcurly = lcurly.getLastChild();
180                }
181                else {
182                    shouldCheckLastRcurly = true;
183                    nextToken = getNextToken(ast);
184                    lcurly = ast.getLastChild();
185                    rcurly = lcurly.getLastChild();
186                }
187                break;
188            case TokenTypes.LITERAL_ELSE:
189                shouldCheckLastRcurly = true;
190                nextToken = getNextToken(ast);
191                lcurly = ast.getFirstChild();
192                rcurly = lcurly.getLastChild();
193                break;
194            case TokenTypes.LITERAL_FINALLY:
195                shouldCheckLastRcurly = true;
196                nextToken = getNextToken(ast);
197                lcurly = ast.getFirstChild();
198                rcurly = lcurly.getLastChild();
199                break;
200            case TokenTypes.CLASS_DEF:
201                final DetailAST child = ast.getLastChild();
202                lcurly = child.getFirstChild();
203                rcurly = child.getLastChild();
204                nextToken = ast;
205                break;
206            case TokenTypes.CTOR_DEF:
207            case TokenTypes.STATIC_INIT:
208            case TokenTypes.INSTANCE_INIT:
209                lcurly = ast.findFirstToken(TokenTypes.SLIST);
210                rcurly = lcurly.getLastChild();
211                nextToken = ast;
212                break;
213            case TokenTypes.METHOD_DEF:
214            case TokenTypes.LITERAL_FOR:
215            case TokenTypes.LITERAL_WHILE:
216            case TokenTypes.LITERAL_DO:
217                lcurly = ast.findFirstToken(TokenTypes.SLIST);
218                //SLIST could be absent if method is abstract, and code like "while(true);"
219                if (lcurly == null) {
220                    return;
221                }
222                rcurly = lcurly.getLastChild();
223                nextToken = ast;
224                break;
225            default:
226                throw new RuntimeException("Unexpected token type ("
227                    + TokenTypes.getTokenName(ast.getType()) + ")");
228        }
229
230        if ((rcurly == null) || (rcurly.getType() != TokenTypes.RCURLY)) {
231            // we need to have both tokens to perform the check
232            return;
233        }
234
235        if (getAbstractOption() == RightCurlyOption.SAME && !hasLineBreakBefore(rcurly)) {
236            log(rcurly, MSG_KEY_LINE_BREAK_BEFORE);
237        }
238
239        if (shouldCheckLastRcurly) {
240            if (rcurly.getLineNo() == nextToken.getLineNo()) {
241                log(rcurly, MSG_KEY_LINE_ALONE, "}");
242            }
243        }
244        else if ((getAbstractOption() == RightCurlyOption.SAME)
245                && (rcurly.getLineNo() != nextToken.getLineNo()))
246        {
247            log(rcurly, MSG_KEY_LINE_SAME, "}");
248        }
249        else if ((getAbstractOption() == RightCurlyOption.ALONE)
250                && (rcurly.getLineNo() == nextToken.getLineNo())
251                && !isEmptyBody(lcurly))
252        {
253            log(rcurly, MSG_KEY_LINE_ALONE, "}");
254        }
255
256        if (!shouldStartLine) {
257            return;
258        }
259        final boolean startsLine =
260                Utils.whitespaceBefore(rcurly.getColumnNo(),
261                        getLines()[rcurly.getLineNo() - 1]);
262
263        if (!startsLine && (lcurly.getLineNo() != rcurly.getLineNo())) {
264            log(rcurly, MSG_KEY_LINE_NEW, "}");
265        }
266    }
267
268    /**
269     * Checks if definition body is empty.
270     * @param lcurly left curly.
271     * @return true if definition body is empty.
272     */
273    private boolean isEmptyBody(DetailAST lcurly)
274    {
275        boolean result = false;
276        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
277            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
278                result = true;
279            }
280        }
281        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
282            result = true;
283        }
284        return result;
285    }
286
287    /**
288     * Finds next token after the given one.
289     * @param ast the given node.
290     * @return the token which represents next lexical item.
291     */
292    private DetailAST getNextToken(DetailAST ast)
293    {
294        DetailAST next = null;
295        DetailAST parent = ast;
296        while ((parent != null) && (next == null)) {
297            next = parent.getNextSibling();
298            parent = parent.getParent();
299        }
300        return CheckUtils.getFirstNode(next);
301    }
302
303    /**
304     * Checks if right curly has line break before.
305     * @param rightCurly
306     *        Right curly token.
307     * @return
308     *        True, if right curly has line break before.
309     */
310    private boolean hasLineBreakBefore(DetailAST rightCurly)
311    {
312        if (rightCurly != null) {
313            final DetailAST previousToken = rightCurly.getPreviousSibling();
314            if (previousToken != null && rightCurly.getLineNo() == previousToken.getLineNo()) {
315                return false;
316            }
317        }
318        return true;
319    }
320}