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