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