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