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