001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 java.util.Arrays;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
027import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
028import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
029
030/**
031 * Abstract base class for all handlers.
032 *
033 */
034public abstract class AbstractExpressionHandler {
035
036    /**
037     * The instance of {@code IndentationCheck} using this handler.
038     */
039    private final IndentationCheck indentCheck;
040
041    /** The AST which is handled by this handler. */
042    private final DetailAST mainAst;
043
044    /** Name used during output to user. */
045    private final String typeName;
046
047    /** Containing AST handler. */
048    private final AbstractExpressionHandler parent;
049
050    /** Indentation amount for this handler. */
051    private IndentLevel indent;
052
053    /**
054     * Construct an instance of this handler with the given indentation check,
055     * name, abstract syntax tree, and parent handler.
056     *
057     * @param indentCheck   the indentation check
058     * @param typeName      the name of the handler
059     * @param expr          the abstract syntax tree
060     * @param parent        the parent handler
061     */
062    protected AbstractExpressionHandler(IndentationCheck indentCheck, String typeName,
063            DetailAST expr, AbstractExpressionHandler parent) {
064        this.indentCheck = indentCheck;
065        this.typeName = typeName;
066        mainAst = expr;
067        this.parent = parent;
068    }
069
070    /**
071     * Check the indentation of the expression we are handling.
072     */
073    public abstract void checkIndentation();
074
075    /**
076     * Get the indentation amount for this handler. For performance reasons,
077     * this value is cached. The first time this method is called, the
078     * indentation amount is computed and stored. On further calls, the stored
079     * value is returned.
080     *
081     * @return the expected indentation amount
082     * @noinspection WeakerAccess
083     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
084     */
085    public final IndentLevel getIndent() {
086        if (indent == null) {
087            indent = getIndentImpl();
088        }
089        return indent;
090    }
091
092    /**
093     * Compute the indentation amount for this handler.
094     *
095     * @return the expected indentation amount
096     */
097    protected IndentLevel getIndentImpl() {
098        return parent.getSuggestedChildIndent(this);
099    }
100
101    /**
102     * Indentation level suggested for a child element. Children don't have
103     * to respect this, but most do.
104     *
105     * @param child  child AST (so suggestion level can differ based on child
106     *                  type)
107     *
108     * @return suggested indentation for child
109     * @noinspection WeakerAccess
110     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
111     */
112    public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
113        return new IndentLevel(getIndent(), getBasicOffset());
114    }
115
116    /**
117     * Log an indentation error.
118     *
119     * @param ast           the expression that caused the error
120     * @param subtypeName   the type of the expression
121     * @param actualIndent  the actual indent level of the expression
122     */
123    protected final void logError(DetailAST ast, String subtypeName,
124                                  int actualIndent) {
125        logError(ast, subtypeName, actualIndent, getIndent());
126    }
127
128    /**
129     * Log an indentation error.
130     *
131     * @param ast            the expression that caused the error
132     * @param subtypeName    the type of the expression
133     * @param actualIndent   the actual indent level of the expression
134     * @param expectedIndent the expected indent level of the expression
135     */
136    protected final void logError(DetailAST ast, String subtypeName,
137                                  int actualIndent, IndentLevel expectedIndent) {
138        final String typeStr;
139
140        if (subtypeName.isEmpty()) {
141            typeStr = "";
142        }
143        else {
144            typeStr = " " + subtypeName;
145        }
146        String messageKey = IndentationCheck.MSG_ERROR;
147        if (expectedIndent.isMultiLevel()) {
148            messageKey = IndentationCheck.MSG_ERROR_MULTI;
149        }
150        indentCheck.indentationLog(ast, messageKey,
151            typeName + typeStr, actualIndent, expectedIndent);
152    }
153
154    /**
155     * Log child indentation error.
156     *
157     * @param ast            the abstract syntax tree that causes the error
158     * @param actualIndent   the actual indent level of the expression
159     * @param expectedIndent the expected indent level of the expression
160     */
161    private void logChildError(DetailAST ast,
162                               int actualIndent,
163                               IndentLevel expectedIndent) {
164        String messageKey = IndentationCheck.MSG_CHILD_ERROR;
165        if (expectedIndent.isMultiLevel()) {
166            messageKey = IndentationCheck.MSG_CHILD_ERROR_MULTI;
167        }
168        indentCheck.indentationLog(ast, messageKey,
169            typeName, actualIndent, expectedIndent);
170    }
171
172    /**
173     * Determines if the given expression is at the start of a line.
174     *
175     * @param ast   the expression to check
176     *
177     * @return true if it is, false otherwise
178     */
179    protected final boolean isOnStartOfLine(DetailAST ast) {
180        return getLineStart(ast) == expandedTabsColumnNo(ast);
181    }
182
183    /**
184     * Searches in given subtree (including given node) for the token
185     * which represents first symbol for this subtree in file.
186     *
187     * @param ast a root of subtree in which the search should be performed.
188     * @return a token which occurs first in the file.
189     * @noinspection WeakerAccess
190     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
191     */
192    public static DetailAST getFirstToken(DetailAST ast) {
193        DetailAST first = ast;
194        DetailAST child = ast.getFirstChild();
195
196        while (child != null) {
197            final DetailAST toTest = getFirstToken(child);
198            if (toTest.getColumnNo() < first.getColumnNo()) {
199                first = toTest;
200            }
201            child = child.getNextSibling();
202        }
203
204        return first;
205    }
206
207    /**
208     * Get the start of the line for the given expression.
209     *
210     * @param ast   the expression to find the start of the line for
211     *
212     * @return the start of the line for the given expression
213     */
214    protected final int getLineStart(DetailAST ast) {
215        return getLineStart(ast.getLineNo());
216    }
217
218    /**
219     * Get the start of the line for the given line number.
220     *
221     * @param lineNo   the line number to find the start for
222     *
223     * @return the start of the line for the given expression
224     */
225    protected final int getLineStart(int lineNo) {
226        return getLineStart(indentCheck.getLine(lineNo - 1));
227    }
228
229    /**
230     * Get the start of the specified line.
231     *
232     * @param line   the specified line number
233     *
234     * @return the start of the specified line
235     */
236    private int getLineStart(String line) {
237        int index = 0;
238        while (Character.isWhitespace(line.charAt(index))) {
239            index++;
240        }
241        return CommonUtil.lengthExpandedTabs(
242            line, index, indentCheck.getIndentationTabWidth());
243    }
244
245    /**
246     * Checks that indentation should be increased after first line in checkLinesIndent().
247     *
248     * @return true if indentation should be increased after
249     *              first line in checkLinesIndent()
250     *         false otherwise
251     */
252    protected boolean shouldIncreaseIndent() {
253        boolean result = true;
254        if (TokenUtil.isOfType(mainAst, TokenTypes.LITERAL_CATCH)) {
255            final DetailAST parameterAst = mainAst.findFirstToken(TokenTypes.PARAMETER_DEF);
256            result = !AnnotationUtil.containsAnnotation(parameterAst);
257        }
258        return result;
259    }
260
261    /**
262     * Check the indentation for a set of lines.
263     *
264     * @param astSet             the set of abstract syntax tree to check
265     * @param indentLevel        the indentation level
266     * @param firstLineMatches   whether or not the first line has to match
267     * @param firstLine          first line of whole expression
268     * @param allowNesting       whether or not subtree nesting is allowed
269     */
270    private void checkLinesIndent(DetailAstSet astSet,
271                                  IndentLevel indentLevel,
272                                  boolean firstLineMatches,
273                                  int firstLine,
274                                  boolean allowNesting) {
275        if (!astSet.isEmpty()) {
276            // check first line
277            final DetailAST startLineAst = astSet.firstLine();
278            int startCol = expandedTabsColumnNo(startLineAst);
279
280            final int realStartCol =
281                getLineStart(indentCheck.getLine(startLineAst.getLineNo() - 1));
282
283            if (firstLineMatches && !allowNesting) {
284                startCol = realStartCol;
285            }
286
287            if (realStartCol == startCol) {
288                checkLineIndent(startLineAst, indentLevel,
289                    firstLineMatches);
290            }
291
292            checkRemainingLines(firstLineMatches, indentLevel, firstLine, astSet);
293
294        }
295    }
296
297    /**
298     * Check the indentation of remaining lines present in the astSet.
299     *
300     * @param firstLineMatches   whether or not the first line has to match
301     * @param indentLevel        the indentation level
302     * @param firstLine          first line of whole expression
303     * @param astSet             the set of abstract syntax tree to check
304     */
305    private void checkRemainingLines(boolean firstLineMatches,
306                                     IndentLevel indentLevel,
307                                     int firstLine,
308                                     DetailAstSet astSet) {
309        // if first line starts the line, following lines are indented
310        // one level; but if the first line of this expression is
311        // nested with the previous expression (which is assumed if it
312        // doesn't start the line) then don't indent more, the first
313        // indentation is absorbed by the nesting
314        final DetailAST startLineAst = astSet.firstLine();
315        final int endLine = astSet.lastLine();
316        IndentLevel level = indentLevel;
317
318        if (shouldIncreaseIndent()
319                && startLineAst.getType() != TokenTypes.ANNOTATION
320                && (firstLineMatches || firstLine > mainAst.getLineNo())) {
321            level = new IndentLevel(indentLevel,
322                    indentCheck.getLineWrappingIndentation());
323        }
324
325        // check following lines
326        for (int index = startLineAst.getLineNo() + 1; index <= endLine; index++) {
327            final Integer col = astSet.getStartColumn(index);
328            // startCol could be null if this line didn't have an
329            // expression that was required to be checked (it could be
330            // checked by a child expression)
331
332            if (col != null) {
333                final DetailAST ast = astSet.getAst(index);
334                final boolean textBlockEndAligned =
335                    ast.getType() == TokenTypes.TEXT_BLOCK_LITERAL_END
336                        && isOnStartOfLine(ast.getParent())
337                        && indentLevel.isAcceptable(expandedTabsColumnNo(ast));
338                if (!textBlockEndAligned) {
339                    checkLineIndent(ast, level, false);
340                }
341            }
342        }
343    }
344
345    /**
346     * Check the indentation for a single-line.
347     *
348     * @param ast           the abstract syntax tree to check
349     * @param indentLevel   the indentation level
350     * @param mustMatch     whether or not the indentation level must match
351     */
352    private void checkLineIndent(DetailAST ast,
353        IndentLevel indentLevel, boolean mustMatch) {
354        final String line = indentCheck.getLine(ast.getLineNo() - 1);
355        final int start = getLineStart(line);
356        final int columnNumber = expandedTabsColumnNo(ast);
357        // if must match is set, it is a violation if the line start is not
358        // at the correct indention level; otherwise, it is an only a
359        // violation if this statement starts the line and it is less than
360        // the correct indentation level
361        if (mustMatch && !indentLevel.isAcceptable(start)
362                || !mustMatch && columnNumber == start && indentLevel.isGreaterThan(start)) {
363            logChildError(ast, start, indentLevel);
364        }
365    }
366
367    /**
368     * Checks indentation on wrapped lines between and including
369     * {@code firstNode} and {@code lastNode}.
370     *
371     * @param firstNode First node to start examining.
372     * @param lastNode Last node to examine inclusively.
373     */
374    protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) {
375        indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode);
376    }
377
378    /**
379     * Checks indentation on wrapped lines between and including
380     * {@code firstNode} and {@code lastNode}.
381     *
382     * @param firstNode First node to start examining.
383     * @param lastNode Last node to examine inclusively.
384     * @param wrappedIndentLevel Indentation all wrapped lines should use.
385     * @param startIndent Indentation first line before wrapped lines used.
386     * @param ignoreFirstLine Test if first line's indentation should be checked or not.
387     */
388    protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode,
389            int wrappedIndentLevel, int startIndent,
390            LineWrappingHandler.LineWrappingOptions ignoreFirstLine) {
391        indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode,
392                wrappedIndentLevel, startIndent, ignoreFirstLine);
393    }
394
395    /**
396     * Check the indent level of the children of the specified parent
397     * expression.
398     *
399     * @param parentNode         the parent whose children we are checking
400     * @param tokenTypes         the token types to check
401     * @param startIndent        the starting indent level
402     * @param firstLineMatches   whether or not the first line needs to match
403     * @param allowNesting       whether or not nested children are allowed
404     */
405    protected final void checkChildren(DetailAST parentNode,
406                                       int[] tokenTypes,
407                                       IndentLevel startIndent,
408                                       boolean firstLineMatches,
409                                       boolean allowNesting) {
410        Arrays.sort(tokenTypes);
411        for (DetailAST child = parentNode.getFirstChild();
412                child != null;
413                child = child.getNextSibling()) {
414            if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0
415                    && shouldCheckIndentationForChild(child)) {
416                checkExpressionSubtree(child, startIndent,
417                    firstLineMatches, allowNesting);
418            }
419        }
420    }
421
422    /**
423     * Decide whether to check indentation for a specific child.
424     *
425     * @param child child AST node
426     * @return true if indentation should be checked
427     */
428    protected boolean shouldCheckIndentationForChild(DetailAST child) {
429        return true;
430    }
431
432    /**
433     * Check the indentation level for an expression subtree.
434     *
435     * @param tree               the expression subtree to check
436     * @param indentLevel        the indentation level
437     * @param firstLineMatches   whether or not the first line has to match
438     * @param allowNesting       whether or not subtree nesting is allowed
439     */
440    protected final void checkExpressionSubtree(
441        DetailAST tree,
442        IndentLevel indentLevel,
443        boolean firstLineMatches,
444        boolean allowNesting
445    ) {
446        final DetailAstSet subtreeAst = new DetailAstSet(indentCheck);
447        final int firstLine = getFirstLine(tree);
448        if (firstLineMatches && !allowNesting) {
449            final DetailAST firstAst = getFirstAstNode(tree);
450            subtreeAst.addAst(firstAst);
451        }
452        findSubtreeAst(subtreeAst, tree, allowNesting);
453
454        checkLinesIndent(subtreeAst, indentLevel, firstLineMatches, firstLine, allowNesting);
455    }
456
457    /**
458     * Get the first line number for given expression.
459     *
460     * @param tree      the expression to find the first line for
461     * @return          the first line of expression
462     */
463    protected static int getFirstLine(DetailAST tree) {
464        return getFirstAstNode(tree).getLineNo();
465    }
466
467    /**
468     * Get the first ast for given expression.
469     *
470     * @param ast         the abstract syntax tree for which the starting ast is to be found
471     *
472     * @return            the first ast of the expression
473     */
474    protected static DetailAST getFirstAstNode(DetailAST ast) {
475
476        DetailAST curNode = ast;
477        DetailAST realStart = ast;
478        while (curNode != null) {
479            if (curNode.getLineNo() < realStart.getLineNo()
480                    || curNode.getLineNo() == realStart.getLineNo()
481                    && Math.min(curNode.getColumnNo(), realStart.getColumnNo())
482                    == curNode.getColumnNo()) {
483                realStart = curNode;
484            }
485            DetailAST toVisit = curNode.getFirstChild();
486            while (curNode != ast && toVisit == null) {
487                toVisit = curNode.getNextSibling();
488                curNode = curNode.getParent();
489            }
490            curNode = toVisit;
491        }
492        return realStart;
493    }
494
495    /**
496     * Get the column number for the start of a given expression, expanding
497     * tabs out into spaces in the process.
498     *
499     * @param ast   the expression to find the start of
500     *
501     * @return the column number for the start of the expression
502     */
503    protected final int expandedTabsColumnNo(DetailAST ast) {
504        final String line =
505            indentCheck.getLine(ast.getLineNo() - 1);
506
507        return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(),
508            indentCheck.getIndentationTabWidth());
509    }
510
511    /**
512     * Find the set of abstract syntax tree for a given subtree.
513     *
514     * @param astSet         the set of ast to add
515     * @param tree           the subtree to examine
516     * @param allowNesting   whether or not to allow nested subtrees
517     */
518    protected final void findSubtreeAst(DetailAstSet astSet, DetailAST tree,
519        boolean allowNesting) {
520        if (!indentCheck.getHandlerFactory().isHandledType(tree.getType())) {
521            final int lineNum = tree.getLineNo();
522            final Integer colNum = astSet.getStartColumn(lineNum);
523
524            final int thisLineColumn = expandedTabsColumnNo(tree);
525            if (colNum == null || thisLineColumn < colNum) {
526                astSet.addAst(tree);
527            }
528
529            // check children
530            for (DetailAST node = tree.getFirstChild();
531                node != null;
532                node = node.getNextSibling()) {
533                findSubtreeAst(astSet, node, allowNesting);
534            }
535        }
536    }
537
538    /**
539     * Accessor for the IndentCheck attribute.
540     *
541     * @return the IndentCheck attribute
542     */
543    protected final IndentationCheck getIndentCheck() {
544        return indentCheck;
545    }
546
547    /**
548     * Accessor for the MainAst attribute.
549     *
550     * @return the MainAst attribute
551     */
552    protected final DetailAST getMainAst() {
553        return mainAst;
554    }
555
556    /**
557     * Accessor for the Parent attribute.
558     *
559     * @return the Parent attribute
560     */
561    protected final AbstractExpressionHandler getParent() {
562        return parent;
563    }
564
565    /**
566     * A shortcut for {@code IndentationCheck} property.
567     *
568     * @return value of basicOffset property of {@code IndentationCheck}
569     */
570    protected final int getBasicOffset() {
571        return indentCheck.getBasicOffset();
572    }
573
574    /**
575     * A shortcut for {@code IndentationCheck} property.
576     *
577     * @return value of braceAdjustment property
578     *         of {@code IndentationCheck}
579     */
580    protected final int getBraceAdjustment() {
581        return indentCheck.getBraceAdjustment();
582    }
583
584    /**
585     * Check the indentation of the right parenthesis.
586     *
587     * @param lparen left parenthesis associated with aRparen
588     * @param rparen parenthesis to check
589     */
590    protected final void checkRightParen(DetailAST lparen, DetailAST rparen) {
591        if (rparen != null) {
592            // the rcurly can either be at the correct indentation,
593            // or not first on the line
594            final int rparenLevel = expandedTabsColumnNo(rparen);
595            // or has <lparen level> + 1 indentation
596            final int lparenLevel = expandedTabsColumnNo(lparen);
597
598            if (rparenLevel != lparenLevel + 1
599                    && !getIndent().isAcceptable(rparenLevel)
600                    && isOnStartOfLine(rparen)) {
601                logError(rparen, "rparen", rparenLevel);
602            }
603        }
604    }
605
606    /**
607     * Check the indentation of the left parenthesis.
608     *
609     * @param lparen parenthesis to check
610     */
611    protected final void checkLeftParen(final DetailAST lparen) {
612        // the rcurly can either be at the correct indentation, or on the
613        // same line as the lcurly
614        if (lparen != null
615                && !getIndent().isAcceptable(expandedTabsColumnNo(lparen))
616                && isOnStartOfLine(lparen)) {
617            logError(lparen, "lparen", expandedTabsColumnNo(lparen));
618        }
619    }
620
621}