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