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