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