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