001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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        final String line = indentCheck.getLine(ast.getLineNo() - 1);
219        return getLineStart(line);
220    }
221
222    /**
223     * Get the start of the specified line.
224     *
225     * @param line   the specified line number
226     *
227     * @return the start of the specified line
228     */
229    protected final int getLineStart(String line) {
230        int index = 0;
231        while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
232            index++;
233        }
234        return CommonUtils.lengthExpandedTabs(
235            line, index, indentCheck.getIndentationTabWidth());
236    }
237
238    /**
239     * Checks that indentation should be increased after first line in checkLinesIndent().
240     * @return true if indentation should be increased after
241     *              first line in checkLinesIndent()
242     *         false otherwise
243     */
244    protected boolean shouldIncreaseIndent() {
245        return true;
246    }
247
248    /**
249     * Check the indentation of consecutive lines for the expression we are
250     * handling.
251     *
252     * @param startLine     the first line to check
253     * @param endLine       the last line to check
254     * @param indentLevel   the required indent level
255     */
256    protected final void checkLinesIndent(int startLine, int endLine,
257        IndentLevel indentLevel) {
258        // check first line
259        checkLineIndent(startLine, indentLevel);
260
261        // check following lines
262        final IndentLevel offsetLevel =
263            new IndentLevel(indentLevel, getBasicOffset());
264        for (int i = startLine + 1; i <= endLine; i++) {
265            checkLineIndent(i, offsetLevel);
266        }
267    }
268
269    /**
270     * Check the indentation for a set of lines.
271     *
272     * @param lines              the set of lines to check
273     * @param indentLevel        the indentation level
274     * @param firstLineMatches   whether or not the first line has to match
275     * @param firstLine          first line of whole expression
276     */
277    private void checkLinesIndent(LineSet lines,
278                                  IndentLevel indentLevel,
279                                  boolean firstLineMatches,
280                                  int firstLine) {
281        if (lines.isEmpty()) {
282            return;
283        }
284
285        // check first line
286        final int startLine = lines.firstLine();
287        final int endLine = lines.lastLine();
288        final int startCol = lines.firstLineCol();
289
290        final int realStartCol =
291            getLineStart(indentCheck.getLine(startLine - 1));
292
293        if (realStartCol == startCol) {
294            checkLineIndent(startLine, startCol, indentLevel,
295                firstLineMatches);
296        }
297
298        // if first line starts the line, following lines are indented
299        // one level; but if the first line of this expression is
300        // nested with the previous expression (which is assumed if it
301        // doesn't start the line) then don't indent more, the first
302        // indentation is absorbed by the nesting
303
304        IndentLevel theLevel = indentLevel;
305        if (firstLineMatches
306            || firstLine > mainAst.getLineNo() && shouldIncreaseIndent()) {
307            theLevel = new IndentLevel(indentLevel, getBasicOffset());
308        }
309
310        // check following lines
311        for (int i = startLine + 1; i <= endLine; i++) {
312            final Integer col = lines.getStartColumn(i);
313            // startCol could be null if this line didn't have an
314            // expression that was required to be checked (it could be
315            // checked by a child expression)
316
317            if (col != null) {
318                checkLineIndent(i, col, theLevel, false);
319            }
320        }
321    }
322
323    /**
324     * Check the indent level for a single line.
325     *
326     * @param lineNum       the line number to check
327     * @param indentLevel   the required indent level
328     */
329    private void checkLineIndent(int lineNum, IndentLevel indentLevel) {
330        final String line = indentCheck.getLine(lineNum - 1);
331        if (!line.isEmpty()) {
332            final int start = getLineStart(line);
333            if (indentLevel.isGreaterThan(start)) {
334                logChildError(lineNum, start, indentLevel);
335            }
336        }
337    }
338
339    /**
340     * Check the indentation for a single line.
341     *
342     * @param lineNum       the number of the line to check
343     * @param colNum        the column number we are starting at
344     * @param indentLevel   the indentation level
345     * @param mustMatch     whether or not the indentation level must match
346     */
347    private void checkLineIndent(int lineNum, int colNum,
348        IndentLevel indentLevel, boolean mustMatch) {
349        final String line = indentCheck.getLine(lineNum - 1);
350        final int start = getLineStart(line);
351        // if must match is set, it is an error if the line start is not
352        // at the correct indention level; otherwise, it is an only an
353        // error if this statement starts the line and it is less than
354        // the correct indentation level
355        if (mustMatch && !indentLevel.isAcceptable(start)
356                || !mustMatch && colNum == start && indentLevel.isGreaterThan(start)) {
357            logChildError(lineNum, start, indentLevel);
358        }
359    }
360
361    /**
362     * Checks indentation on wrapped lines between and including
363     * {@code firstNode} and {@code lastNode}.
364     *
365     * @param firstNode First node to start examining.
366     * @param lastNode Last node to examine inclusively.
367     */
368    protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) {
369        indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode);
370    }
371
372    /**
373     * Check the indent level of the children of the specified parent
374     * expression.
375     *
376     * @param parentNode         the parent whose children we are checking
377     * @param tokenTypes         the token types to check
378     * @param startIndent        the starting indent level
379     * @param firstLineMatches   whether or not the first line needs to match
380     * @param allowNesting       whether or not nested children are allowed
381     */
382    protected final void checkChildren(DetailAST parentNode,
383                                       int[] tokenTypes,
384                                       IndentLevel startIndent,
385                                       boolean firstLineMatches,
386                                       boolean allowNesting) {
387        Arrays.sort(tokenTypes);
388        for (DetailAST child = parentNode.getFirstChild();
389                child != null;
390                child = child.getNextSibling()) {
391            if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) {
392                checkExpressionSubtree(child, startIndent,
393                    firstLineMatches, allowNesting);
394            }
395        }
396    }
397
398    /**
399     * Check the indentation level for an expression subtree.
400     *
401     * @param tree               the expression subtree to check
402     * @param indentLevel        the indentation level
403     * @param firstLineMatches   whether or not the first line has to match
404     * @param allowNesting       whether or not subtree nesting is allowed
405     */
406    protected final void checkExpressionSubtree(
407        DetailAST tree,
408        IndentLevel indentLevel,
409        boolean firstLineMatches,
410        boolean allowNesting
411    ) {
412        final LineSet subtreeLines = new LineSet();
413        final int firstLine = getFirstLine(Integer.MAX_VALUE, tree);
414        if (firstLineMatches && !allowNesting) {
415            subtreeLines.addLineAndCol(firstLine,
416                getLineStart(indentCheck.getLine(firstLine - 1)));
417        }
418        findSubtreeLines(subtreeLines, tree, allowNesting);
419
420        checkLinesIndent(subtreeLines, indentLevel, firstLineMatches, firstLine);
421    }
422
423    /**
424     * Get the first line for a given expression.
425     *
426     * @param startLine   the line we are starting from
427     * @param tree        the expression to find the first line for
428     *
429     * @return the first line of the expression
430     */
431    protected final int getFirstLine(int startLine, DetailAST tree) {
432        int realStart = startLine;
433        final int currLine = tree.getLineNo();
434        if (currLine < realStart) {
435            realStart = currLine;
436        }
437
438        // check children
439        for (DetailAST node = tree.getFirstChild();
440            node != null;
441            node = node.getNextSibling()) {
442            realStart = getFirstLine(realStart, node);
443        }
444
445        return realStart;
446    }
447
448    /**
449     * Get the column number for the start of a given expression, expanding
450     * tabs out into spaces in the process.
451     *
452     * @param ast   the expression to find the start of
453     *
454     * @return the column number for the start of the expression
455     */
456    protected final int expandedTabsColumnNo(DetailAST ast) {
457        final String line =
458            indentCheck.getLine(ast.getLineNo() - 1);
459
460        return CommonUtils.lengthExpandedTabs(line, ast.getColumnNo(),
461            indentCheck.getIndentationTabWidth());
462    }
463
464    /**
465     * Find the set of lines for a given subtree.
466     *
467     * @param lines          the set of lines to add to
468     * @param tree           the subtree to examine
469     * @param allowNesting   whether or not to allow nested subtrees
470     */
471    protected final void findSubtreeLines(LineSet lines, DetailAST tree,
472        boolean allowNesting) {
473        if (indentCheck.getHandlerFactory().isHandledType(tree.getType())) {
474            return;
475        }
476
477        final int lineNum = tree.getLineNo();
478        final Integer colNum = lines.getStartColumn(lineNum);
479
480        final int thisLineColumn = expandedTabsColumnNo(tree);
481        if (colNum == null || thisLineColumn < colNum) {
482            lines.addLineAndCol(lineNum, thisLineColumn);
483        }
484
485        // check children
486        for (DetailAST node = tree.getFirstChild();
487            node != null;
488            node = node.getNextSibling()) {
489            findSubtreeLines(lines, node, allowNesting);
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     * @return value of basicOffset property of {@code IndentationCheck}
540     */
541    protected final int getBasicOffset() {
542        return indentCheck.getBasicOffset();
543    }
544
545    /**
546     * A shortcut for {@code IndentationCheck} property.
547     * @return value of braceAdjustment property
548     *         of {@code IndentationCheck}
549     */
550    protected final int getBraceAdjustment() {
551        return indentCheck.getBraceAdjustment();
552    }
553
554    /**
555     * Check the indentation of the right parenthesis.
556     * @param rparen parenthesis to check
557     * @param lparen left parenthesis associated with aRparen
558     */
559    protected final void checkRParen(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     * @param lparen parenthesis to check
578     */
579    protected final void checkLParen(final DetailAST lparen) {
580        // the rcurly can either be at the correct indentation, or on the
581        // same line as the lcurly
582        if (lparen == null
583            || getIndent().isAcceptable(expandedTabsColumnNo(lparen))
584            || !isOnStartOfLine(lparen)) {
585            return;
586        }
587        logError(lparen, "lparen", expandedTabsColumnNo(lparen));
588    }
589}