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