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.indentation;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023
024/**
025 * Handler for parents of blocks ('if', 'else', 'while', etc).
026 * <P>
027 * The "block" handler classes use a common superclass BlockParentHandler,
028 * employing the Template Method pattern.
029 * </P>
030 *
031 * <UL>
032 *   <LI>template method to get the lcurly</LI>
033 *   <LI>template method to get the rcurly</LI>
034 *   <LI>if curlys aren't present, then template method to get expressions
035 *       is called</LI>
036 *   <LI>now all the repetitous code which checks for BOL, if curlys are on
037 *       same line, etc. can be collapsed into  the superclass</LI>
038 * </UL>
039 *
040 *
041 * @author jrichard
042 */
043public class BlockParentHandler extends ExpressionHandler
044{
045    /**
046     * Children checked by parent handlers.
047     */
048    private static final int[] CHECKED_CHILDREN = new int[] {
049        TokenTypes.VARIABLE_DEF,
050        TokenTypes.EXPR,
051        TokenTypes.OBJBLOCK,
052        TokenTypes.LITERAL_BREAK,
053        TokenTypes.LITERAL_RETURN,
054        TokenTypes.LITERAL_THROW,
055        TokenTypes.LITERAL_CONTINUE,
056    };
057
058    /**
059     * Returns array of token types which should be checked among childrens.
060     * @return array of token types to check.
061     */
062    protected int[] getCheckedChildren()
063    {
064        return CHECKED_CHILDREN;
065    }
066
067    /**
068     * Construct an instance of this handler with the given indentation check,
069     * name, abstract syntax tree, and parent handler.
070     *
071     * @param indentCheck   the indentation check
072     * @param name          the name of the handler
073     * @param ast           the abstract syntax tree
074     * @param parent        the parent handler
075     */
076    public BlockParentHandler(IndentationCheck indentCheck,
077        String name, DetailAST ast, ExpressionHandler parent)
078    {
079        super(indentCheck, name, ast, parent);
080    }
081
082    /**
083     * Get the top level expression being managed by this handler.
084     *
085     * @return the top level expression
086     */
087    protected DetailAST getToplevelAST()
088    {
089        return getMainAst();
090    }
091
092    /**
093     * Check the indent of the top level token.
094     */
095    protected void checkToplevelToken()
096    {
097        final DetailAST toplevel = getToplevelAST();
098
099        if ((toplevel == null)
100            || getLevel().accept(expandedTabsColumnNo(toplevel)) || hasLabelBefore())
101        {
102            return;
103        }
104        if (!toplevelMustStartLine() && !startsLine(toplevel)) {
105            return;
106        }
107        logError(toplevel, "", expandedTabsColumnNo(toplevel));
108    }
109
110    /**
111     * Check if the top level token has label before.
112     * @return true if the top level token has label before.
113     */
114    protected boolean hasLabelBefore()
115    {
116        final DetailAST parent = getToplevelAST().getParent();
117        return parent != null && parent.getType() == TokenTypes.LABELED_STAT
118            && parent.getLineNo() == getToplevelAST().getLineNo();
119    }
120
121    /**
122     * Determines if the top level token must start the line.
123     *
124     * @return true
125     */
126    protected boolean toplevelMustStartLine()
127    {
128        return true;
129    }
130
131    /**
132     * Determines if this block expression has curly braces.
133     *
134     * @return true if curly braces are present, false otherwise
135     */
136    protected boolean hasCurlys()
137    {
138        return (getLCurly() != null) && (getRCurly() != null);
139    }
140
141    /**
142     * Get the left curly brace portion of the expression we are handling.
143     *
144     * @return the left curly brace expression
145     */
146    protected DetailAST getLCurly()
147    {
148        return getMainAst().findFirstToken(TokenTypes.SLIST);
149    }
150
151    /**
152     * Get the right curly brace portion of the expression we are handling.
153     *
154     * @return the right curly brace expression
155     */
156    protected DetailAST getRCurly()
157    {
158        final DetailAST slist = getMainAst().findFirstToken(TokenTypes.SLIST);
159        if (slist == null) {
160            return null;
161        }
162
163        return slist.findFirstToken(TokenTypes.RCURLY);
164    }
165
166    /**
167     * Check the indentation of the left curly brace.
168     */
169    protected void checkLCurly()
170    {
171        // the lcurly can either be at the correct indentation, or nested
172        // with a previous expression
173        final DetailAST lcurly = getLCurly();
174        final int lcurlyPos = expandedTabsColumnNo(lcurly);
175
176        if ((lcurly == null)
177            || curlyLevel().accept(lcurlyPos)
178            || !startsLine(lcurly))
179        {
180            return;
181        }
182
183        logError(lcurly, "lcurly", lcurlyPos);
184    }
185
186    /**
187     * Get the expected indentation level for the curly braces.
188     *
189     * @return the curly brace indentation level
190     */
191    protected IndentLevel curlyLevel()
192    {
193        return new IndentLevel(getLevel(), getBraceAdjustement());
194    }
195
196    /**
197     * Determines if the right curly brace must be at the start of the line.
198     *
199     * @return true
200     */
201    protected boolean rcurlyMustStart()
202    {
203        return true;
204    }
205
206    /**
207     * Determines if child elements within the expression may be nested.
208     *
209     * @return false
210     */
211    protected boolean childrenMayNest()
212    {
213        return false;
214    }
215
216    /**
217     * Check the indentation of the right curly brace.
218     */
219    protected void checkRCurly()
220    {
221        // the rcurly can either be at the correct indentation, or
222        // on the same line as the lcurly
223        final DetailAST lcurly = getLCurly();
224        final DetailAST rcurly = getRCurly();
225        final int rcurlyPos = expandedTabsColumnNo(rcurly);
226
227        if ((rcurly == null)
228            || curlyLevel().accept(rcurlyPos)
229            || (!rcurlyMustStart() && !startsLine(rcurly))
230            || areOnSameLine(rcurly, lcurly))
231        {
232            return;
233        }
234        logError(rcurly, "rcurly", rcurlyPos, curlyLevel());
235    }
236
237    /**
238     * Get the child element that is not a list of statements.
239     *
240     * @return the non-list child element
241     */
242    protected DetailAST getNonlistChild()
243    {
244        return getMainAst().findFirstToken(TokenTypes.RPAREN).getNextSibling();
245    }
246
247    /**
248     * Check the indentation level of a child that is not a list of statements.
249     */
250    private void checkNonlistChild()
251    {
252        final DetailAST nonlist = getNonlistChild();
253        if (nonlist == null) {
254            return;
255        }
256
257        final IndentLevel expected = new IndentLevel(getLevel(), getBasicOffset());
258        checkExpressionSubtree(nonlist, expected, false, false);
259    }
260
261    /**
262     * Get the child element representing the list of statements.
263     *
264     * @return the statement list child
265     */
266    protected DetailAST getListChild()
267    {
268        return getMainAst().findFirstToken(TokenTypes.SLIST);
269    }
270
271    /**
272     * Get the right parenthesis portion of the expression we are handling.
273     *
274     * @return the right parenthis expression
275     */
276    protected DetailAST getRParen()
277    {
278        return getMainAst().findFirstToken(TokenTypes.RPAREN);
279    }
280
281    /**
282     * Get the left parenthesis portion of the expression we are handling.
283     *
284     * @return the left parenthis expression
285     */
286    protected DetailAST getLParen()
287    {
288        return getMainAst().findFirstToken(TokenTypes.LPAREN);
289    }
290
291    @Override
292    public void checkIndentation()
293    {
294        checkToplevelToken();
295        // seperate to allow for eventual configuration
296        checkLParen(getLParen());
297        checkRParen(getLParen(), getRParen());
298        if (hasCurlys()) {
299            checkLCurly();
300            checkRCurly();
301        }
302        final DetailAST listChild = getListChild();
303        if (listChild != null) {
304            // NOTE: switch statements usually don't have curlys
305            if (!hasCurlys() || !areOnSameLine(getLCurly(), getRCurly())) {
306                checkChildren(listChild,
307                              getCheckedChildren(),
308                              getChildrenExpectedLevel(),
309                              true,
310                              childrenMayNest());
311            }
312        }
313        else {
314            checkNonlistChild();
315        }
316    }
317
318    /**
319     * @return indentation level expected for children
320     */
321    protected IndentLevel getChildrenExpectedLevel()
322    {
323        // if we have multileveled expected level then we should
324        // try to suggest single level to children using curlies'
325        // levels.
326        if (getLevel().isMultiLevel() && hasCurlys()
327            && !areOnSameLine(getLCurly(), getRCurly()))
328        {
329            if (startsLine(getLCurly())) {
330                return new IndentLevel(expandedTabsColumnNo(getLCurly()) + getBasicOffset());
331            }
332            else if (startsLine(getRCurly())) {
333                final IndentLevel level = new IndentLevel(curlyLevel(), getBasicOffset());
334                level.addAcceptedIndent(level.getFirstIndentLevel() + getLineWrappingIndent());
335                return level;
336            }
337        }
338        return new IndentLevel(getLevel(), getBasicOffset());
339    }
340
341    @Override
342    public IndentLevel suggestedChildLevel(ExpressionHandler child)
343    {
344        return getChildrenExpectedLevel();
345    }
346
347    /**
348     * A shortcut for <code>IndentationCheck</code> property.
349     * @return value of lineWrappingIndentation property
350     *         of <code>IndentationCheck</code>
351     */
352    private int getLineWrappingIndent()
353    {
354        return getIndentCheck().getLineWrappingIndentation();
355    }
356}