001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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.indentation;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
025
026/**
027 * Handler for parents of blocks ('if', 'else', 'while', etc).
028 * <P>
029 * The "block" handler classes use a common superclass BlockParentHandler,
030 * employing the Template Method pattern.
031 * </P>
032 *
033 * <UL>
034 *   <LI>template method to get the lcurly</LI>
035 *   <LI>template method to get the rcurly</LI>
036 *   <LI>if curlies aren't present, then template method to get expressions
037 *       is called</LI>
038 *   <LI>now all the repetitious code which checks for BOL, if curlies are on
039 *       same line, etc. can be collapsed into the superclass</LI>
040 * </UL>
041 *
042 *
043 */
044public class BlockParentHandler extends AbstractExpressionHandler {
045
046    /**
047     * Children checked by parent handlers.
048     */
049    private static final int[] CHECKED_CHILDREN = {
050        TokenTypes.VARIABLE_DEF,
051        TokenTypes.EXPR,
052        TokenTypes.ANNOTATION,
053        TokenTypes.OBJBLOCK,
054        TokenTypes.LITERAL_BREAK,
055        TokenTypes.LITERAL_RETURN,
056        TokenTypes.LITERAL_THROW,
057        TokenTypes.LITERAL_CONTINUE,
058        TokenTypes.CTOR_CALL,
059        TokenTypes.SUPER_CTOR_CALL,
060    };
061
062    /**
063     * Construct an instance of this handler with the given indentation check,
064     * name, abstract syntax tree, and parent handler.
065     *
066     * @param indentCheck   the indentation check
067     * @param name          the name of the handler
068     * @param ast           the abstract syntax tree
069     * @param parent        the parent handler
070     * @noinspection WeakerAccess
071     */
072    public BlockParentHandler(IndentationCheck indentCheck,
073        String name, DetailAST ast, AbstractExpressionHandler parent) {
074        super(indentCheck, name, ast, parent);
075    }
076
077    /**
078     * Returns array of token types which should be checked among children.
079     *
080     * @return array of token types to check.
081     */
082    protected int[] getCheckedChildren() {
083        return CHECKED_CHILDREN.clone();
084    }
085
086    /**
087     * Get the top level expression being managed by this handler.
088     *
089     * @return the top level expression
090     */
091    protected DetailAST getTopLevelAst() {
092        return getMainAst();
093    }
094
095    /**
096     * Check the indent of the top level token.
097     */
098    protected void checkTopLevelToken() {
099        final DetailAST topLevel = getTopLevelAst();
100
101        if (topLevel != null
102                && !getIndent().isAcceptable(expandedTabsColumnNo(topLevel))
103                && isOnStartOfLine(topLevel)) {
104            logError(topLevel, "", expandedTabsColumnNo(topLevel));
105        }
106    }
107
108    /**
109     * Determines if this block expression has curly braces.
110     *
111     * @return true if curly braces are present, false otherwise
112     */
113    private boolean hasCurlies() {
114        return getLeftCurly() != null && getRightCurly() != null;
115    }
116
117    /**
118     * Get the left curly brace portion of the expression we are handling.
119     *
120     * @return the left curly brace expression
121     */
122    protected DetailAST getLeftCurly() {
123        return getMainAst().findFirstToken(TokenTypes.SLIST);
124    }
125
126    /**
127     * Get the right curly brace portion of the expression we are handling.
128     *
129     * @return the right curly brace expression
130     */
131    protected DetailAST getRightCurly() {
132        final DetailAST slist = getMainAst().findFirstToken(TokenTypes.SLIST);
133        return slist.findFirstToken(TokenTypes.RCURLY);
134    }
135
136    /**
137     * Check the indentation of the left curly brace.
138     */
139    private void checkLeftCurly() {
140        // the lcurly can either be at the correct indentation, or nested
141        // with a previous expression
142        final DetailAST lcurly = getLeftCurly();
143        final int lcurlyPos = expandedTabsColumnNo(lcurly);
144
145        if (!curlyIndent().isAcceptable(lcurlyPos) && isOnStartOfLine(lcurly)) {
146            logError(lcurly, "lcurly", lcurlyPos, curlyIndent());
147        }
148    }
149
150    /**
151     * Get the expected indentation level for the curly braces.
152     *
153     * @return the curly brace indentation level
154     */
155    protected IndentLevel curlyIndent() {
156        final DetailAST lcurly = getLeftCurly();
157        IndentLevel expIndentLevel = new IndentLevel(getIndent(), getBraceAdjustment());
158        if (!isOnStartOfLine(lcurly)
159            || lcurly.getParent().getType() == TokenTypes.INSTANCE_INIT) {
160            expIndentLevel = new IndentLevel(getIndent(), 0);
161        }
162
163        return expIndentLevel;
164    }
165
166    /**
167     * Determines if child elements within the expression may be nested.
168     *
169     * @return false
170     */
171    protected boolean canChildrenBeNested() {
172        return false;
173    }
174
175    /**
176     * Check the indentation of the right curly brace.
177     */
178    private void checkRightCurly() {
179        final DetailAST rcurly = getRightCurly();
180        final int rcurlyPos = expandedTabsColumnNo(rcurly);
181
182        if (!curlyIndent().isAcceptable(rcurlyPos)
183                && isOnStartOfLine(rcurly)) {
184            logError(rcurly, "rcurly", rcurlyPos, curlyIndent());
185        }
186    }
187
188    /**
189     * Get the child element that is not a list of statements.
190     *
191     * @return the non-list child element
192     */
193    protected DetailAST getNonListChild() {
194        return getMainAst().findFirstToken(TokenTypes.RPAREN).getNextSibling();
195    }
196
197    /**
198     * Check the indentation level of a child that is not a list of statements.
199     */
200    private void checkNonListChild() {
201        final DetailAST nonList = getNonListChild();
202        if (nonList != null) {
203            final IndentLevel expected = new IndentLevel(getIndent(), getBasicOffset());
204            checkExpressionSubtree(nonList, expected, false, false);
205
206            final DetailAST nonListStartAst = getFirstAstNode(nonList);
207            if (nonList != nonListStartAst) {
208                checkExpressionSubtree(nonListStartAst, expected, false, false);
209            }
210        }
211    }
212
213    /**
214     * Get the child element representing the list of statements.
215     *
216     * @return the statement list child
217     */
218    protected DetailAST getListChild() {
219        return getMainAst().findFirstToken(TokenTypes.SLIST);
220    }
221
222    /**
223     * Get the right parenthesis portion of the expression we are handling.
224     *
225     * @return the right parenthesis expression
226     */
227    private DetailAST getRightParen() {
228        return getMainAst().findFirstToken(TokenTypes.RPAREN);
229    }
230
231    /**
232     * Get the left parenthesis portion of the expression we are handling.
233     *
234     * @return the left parenthesis expression
235     */
236    private DetailAST getLeftParen() {
237        return getMainAst().findFirstToken(TokenTypes.LPAREN);
238    }
239
240    @Override
241    public void checkIndentation() {
242        checkTopLevelToken();
243        // separate to allow for eventual configuration
244        checkLeftParen(getLeftParen());
245        checkRightParen(getLeftParen(), getRightParen());
246        if (hasCurlies()) {
247            checkLeftCurly();
248            checkRightCurly();
249        }
250        final DetailAST listChild = getListChild();
251        if (listChild == null) {
252            checkNonListChild();
253        }
254        else {
255            // NOTE: switch statements usually don't have curlies
256            if (!hasCurlies() || !TokenUtil.areOnSameLine(getLeftCurly(), getRightCurly())) {
257                checkChildren(listChild,
258                        getCheckedChildren(),
259                        getChildrenExpectedIndent(),
260                        true,
261                        canChildrenBeNested());
262            }
263        }
264    }
265
266    /**
267     * Gets indentation level expected for children.
268     *
269     * @return indentation level expected for children
270     */
271    protected IndentLevel getChildrenExpectedIndent() {
272        IndentLevel indentLevel = new IndentLevel(getIndent(), getBasicOffset());
273        // if we have multileveled expected level then we should
274        // try to suggest single level to children using curlies'
275        // levels.
276        if (getIndent().isMultiLevel() && hasCurlies()) {
277            if (isOnStartOfLine(getLeftCurly())) {
278                indentLevel = new IndentLevel(expandedTabsColumnNo(getLeftCurly())
279                        + getBasicOffset());
280            }
281            else if (isOnStartOfLine(getRightCurly())) {
282                final IndentLevel level = new IndentLevel(curlyIndent(), getBasicOffset());
283                indentLevel = IndentLevel.addAcceptable(level, level.getFirstIndentLevel()
284                        + getLineWrappingIndent());
285            }
286        }
287        if (hasCurlies() && isOnStartOfLine(getLeftCurly())) {
288            indentLevel = IndentLevel.addAcceptable(indentLevel,
289                    curlyIndent().getFirstIndentLevel() + getBasicOffset());
290        }
291        return indentLevel;
292    }
293
294    @Override
295    public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
296        return getChildrenExpectedIndent();
297    }
298
299    /**
300     * A shortcut for {@code IndentationCheck} property.
301     *
302     * @return value of lineWrappingIndentation property
303     *         of {@code IndentationCheck}
304     */
305    private int getLineWrappingIndent() {
306        return getIndentCheck().getLineWrappingIndentation();
307    }
308
309}