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 mShouldStartLine = true;
077
078    /**
079     * Sets the right curly option to same.
080     */
081    public RightCurlyCheck()
082    {
083        super(RightCurlyOption.SAME, RightCurlyOption.class);
084    }
085
086    /**
087     * Does the check need to check if rcurly starts line.
088     * @param aFlag new value of this property.
089     */
090    public void setShouldStartLine(boolean aFlag)
091    {
092        mShouldStartLine = aFlag;
093    }
094
095    @Override
096    public int[] getDefaultTokens()
097    {
098        return new int[] {
099            TokenTypes.LITERAL_TRY,
100            TokenTypes.LITERAL_CATCH,
101            TokenTypes.LITERAL_FINALLY,
102            TokenTypes.LITERAL_IF,
103            TokenTypes.LITERAL_ELSE,
104        };
105    }
106
107    @Override
108    public int[] getAcceptableTokens()
109    {
110        return new int[] {
111            TokenTypes.LITERAL_TRY,
112            TokenTypes.LITERAL_CATCH,
113            TokenTypes.LITERAL_FINALLY,
114            TokenTypes.LITERAL_IF,
115            TokenTypes.LITERAL_ELSE,
116            TokenTypes.CLASS_DEF,
117            TokenTypes.METHOD_DEF,
118            TokenTypes.CTOR_DEF,
119            TokenTypes.LITERAL_FOR,
120            TokenTypes.LITERAL_WHILE,
121            TokenTypes.LITERAL_DO,
122            TokenTypes.STATIC_INIT,
123            TokenTypes.INSTANCE_INIT,
124        };
125    }
126
127    @Override
128    public void visitToken(DetailAST aAST)
129    {
130        // Attempt to locate the tokens to do the check
131        DetailAST rcurly;
132        DetailAST lcurly;
133        DetailAST nextToken;
134        boolean shouldCheckLastRcurly = false;
135
136        switch (aAST.getType()) {
137        case TokenTypes.LITERAL_TRY:
138            lcurly = aAST.getFirstChild();
139            nextToken = lcurly.getNextSibling();
140            rcurly = lcurly.getLastChild();
141            break;
142        case TokenTypes.LITERAL_CATCH:
143            nextToken = aAST.getNextSibling();
144            lcurly = aAST.getLastChild();
145            rcurly = lcurly.getLastChild();
146            if (nextToken == null) {
147                shouldCheckLastRcurly = true;
148                nextToken = getNextToken(aAST);
149            }
150            break;
151        case TokenTypes.LITERAL_IF:
152            nextToken = aAST.findFirstToken(TokenTypes.LITERAL_ELSE);
153            if (nextToken != null) {
154                lcurly = nextToken.getPreviousSibling();
155                rcurly = lcurly.getLastChild();
156            }
157            else {
158                shouldCheckLastRcurly = true;
159                nextToken = getNextToken(aAST);
160                lcurly = aAST.getLastChild();
161                rcurly = lcurly.getLastChild();
162            }
163            break;
164        case TokenTypes.LITERAL_ELSE:
165            shouldCheckLastRcurly = true;
166            nextToken = getNextToken(aAST);
167            lcurly = aAST.getFirstChild();
168            rcurly = lcurly.getLastChild();
169            break;
170        case TokenTypes.LITERAL_FINALLY:
171            shouldCheckLastRcurly = true;
172            nextToken = getNextToken(aAST);
173            lcurly = aAST.getFirstChild();
174            rcurly = lcurly.getLastChild();
175            break;
176        case TokenTypes.CLASS_DEF:
177            final DetailAST child = aAST.getLastChild();
178            lcurly = child.getFirstChild();
179            rcurly = child.getLastChild();
180            nextToken = aAST;
181            break;
182        case TokenTypes.CTOR_DEF:
183        case TokenTypes.STATIC_INIT:
184        case TokenTypes.INSTANCE_INIT:
185            lcurly = aAST.findFirstToken(TokenTypes.SLIST);
186            rcurly = lcurly.getLastChild();
187            nextToken = aAST;
188            break;
189        case TokenTypes.METHOD_DEF:
190        case TokenTypes.LITERAL_FOR:
191        case TokenTypes.LITERAL_WHILE:
192        case TokenTypes.LITERAL_DO:
193            lcurly = aAST.findFirstToken(TokenTypes.SLIST);
194            //SLIST could be absent if method is abstract, and code like "while(true);"
195            if (lcurly == null) {
196                return;
197            }
198            rcurly = lcurly.getLastChild();
199            nextToken = aAST;
200            break;
201        default:
202            throw new RuntimeException("Unexpected token type ("
203                    + TokenTypes.getTokenName(aAST.getType()) + ")");
204        }
205
206        if ((rcurly == null) || (rcurly.getType() != TokenTypes.RCURLY)) {
207            // we need to have both tokens to perform the check
208            return;
209        }
210
211        if (getAbstractOption() == RightCurlyOption.SAME && !hasLineBreakBefore(rcurly)) {
212            log(rcurly, "line.break.before");
213        }
214
215        if (shouldCheckLastRcurly) {
216            if (rcurly.getLineNo() == nextToken.getLineNo()) {
217                log(rcurly, "line.alone", "}");
218            }
219        }
220        else if ((getAbstractOption() == RightCurlyOption.SAME)
221                && (rcurly.getLineNo() != nextToken.getLineNo()))
222        {
223            log(rcurly, "line.same", "}");
224        }
225        else if ((getAbstractOption() == RightCurlyOption.ALONE)
226                && (rcurly.getLineNo() == nextToken.getLineNo())
227                && !isEmptyBody(lcurly))
228        {
229            log(rcurly, "line.alone", "}");
230        }
231
232        if (!mShouldStartLine) {
233            return;
234        }
235        final boolean startsLine =
236                Utils.whitespaceBefore(rcurly.getColumnNo(),
237                        getLines()[rcurly.getLineNo() - 1]);
238
239        if (!startsLine && (lcurly.getLineNo() != rcurly.getLineNo())) {
240            log(rcurly, "line.new", "}");
241        }
242    }
243
244    /**
245     * Checks if definition body is empty.
246     * @param aLcurly left curly.
247     * @return true if definition body is empty.
248     */
249    private boolean isEmptyBody(DetailAST aLcurly)
250    {
251        boolean result = false;
252        if (aLcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
253            if (aLcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
254                result = true;
255            }
256        }
257        else if (aLcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
258            result = true;
259        }
260        return result;
261    }
262
263    /**
264     * Finds next token after the given one.
265     * @param aAST the given node.
266     * @return the token which represents next lexical item.
267     */
268    private DetailAST getNextToken(DetailAST aAST)
269    {
270        DetailAST next = null;
271        DetailAST parent = aAST;
272        while ((parent != null) && (next == null)) {
273            next = parent.getNextSibling();
274            parent = parent.getParent();
275        }
276        return CheckUtils.getFirstNode(next);
277    }
278
279    /**
280     * Checks if right curly has line break before.
281     * @param aRightCurly
282     *        Right curly token.
283     * @return
284     *        True, if right curly has line break before.
285     */
286    private boolean hasLineBreakBefore(DetailAST aRightCurly)
287    {
288        if (aRightCurly != null) {
289            final DetailAST previousToken = aRightCurly.getPreviousSibling();
290            if (previousToken != null && aRightCurly.getLineNo() == previousToken.getLineNo()) {
291                return false;
292            }
293        }
294        return true;
295    }
296}