View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  Oliver Burn
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  package com.puppycrawl.tools.checkstyle.checks.indentation;
20  
21  import com.puppycrawl.tools.checkstyle.api.DetailAST;
22  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
23  import com.puppycrawl.tools.checkstyle.api.Utils;
24  import java.util.Arrays;
25  
26  /**
27   * Abstract base class for all handlers.
28   *
29   * @author jrichard
30   */
31  public abstract class ExpressionHandler
32  {
33      /**
34       * The instance of <code>IndentationCheck</code> using this handler.
35       */
36      private final IndentationCheck indentCheck;
37  
38      /** the AST which is handled by this handler */
39      private final DetailAST mainAst;
40  
41      /** name used during output to user */
42      private final String typeName;
43  
44      /** containing AST handler */
45      private final ExpressionHandler parent;
46  
47      /** indentation amount for this handler */
48      private IndentLevel level;
49  
50      /**
51       * Construct an instance of this handler with the given indentation check,
52       * name, abstract syntax tree, and parent handler.
53       *
54       * @param indentCheck   the indentation check
55       * @param typeName      the name of the handler
56       * @param expr          the abstract syntax tree
57       * @param parent        the parent handler
58       */
59      public ExpressionHandler(IndentationCheck indentCheck,
60              String typeName, DetailAST expr, ExpressionHandler parent)
61      {
62          this.indentCheck = indentCheck;
63          this.typeName = typeName;
64          mainAst = expr;
65          this.parent = parent;
66      }
67  
68      /**
69       * Get the indentation amount for this handler. For performance reasons,
70       * this value is cached. The first time this method is called, the
71       * indentation amount is computed and stored. On further calls, the stored
72       * value is returned.
73       *
74       * @return the expected indentation amount
75       */
76      public final IndentLevel getLevel()
77      {
78          if (level == null) {
79              level = getLevelImpl();
80          }
81          return level;
82      }
83  
84      /**
85       * Compute the indentation amount for this handler.
86       *
87       * @return the expected indentation amount
88       */
89      protected IndentLevel getLevelImpl()
90      {
91          return parent.suggestedChildLevel(this);
92      }
93  
94      /**
95       * Indentation level suggested for a child element. Children don't have
96       * to respect this, but most do.
97       *
98       * @param child  child AST (so suggestion level can differ based on child
99       *                  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 }