KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > puppycrawl > tools > checkstyle > checks > indentation > ExpressionHandler


1 ////////////////////////////////////////////////////////////////////////////////
2
// checkstyle: Checks Java source code for adherence to a set of rules.
3
// Copyright (C) 2001-2005 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 java.util.Arrays JavaDoc;
22
23 import com.puppycrawl.tools.checkstyle.api.DetailAST;
24 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
25 import com.puppycrawl.tools.checkstyle.api.Utils;
26
27 /**
28  * Abstract base class for all handlers.
29  *
30  * @author jrichard
31  */

32 public abstract class ExpressionHandler
33 {
34     /**
35      * The instance of <code>IndentationCheck</code> using this handler.
36      */

37     private IndentationCheck mIndentCheck;
38
39     /** the AST which is handled by this handler */
40     private DetailAST mMainAst;
41
42     /** name used during output to user */
43     private String JavaDoc mTypeName;
44
45     /** containing AST handler */
46     private ExpressionHandler mParent;
47
48     /** indentation amount for this handler */
49     private IndentLevel mLevel;
50
51     /**
52      * Construct an instance of this handler with the given indentation check,
53      * name, abstract syntax tree, and parent handler.
54      *
55      * @param aIndentCheck the indentation check
56      * @param aTypeName the name of the handler
57      * @param aExpr the abstract syntax tree
58      * @param aParent the parent handler
59      */

60     public ExpressionHandler(IndentationCheck aIndentCheck,
61             String JavaDoc aTypeName, DetailAST aExpr, ExpressionHandler aParent)
62     {
63         mIndentCheck = aIndentCheck;
64         mTypeName = aTypeName;
65         mMainAst = aExpr;
66         mParent = aParent;
67     }
68
69     /**
70      * Get the indentation amount for this handler. For performance reasons,
71      * this value is cached. The first time this method is called, the
72      * indentation amount is computed and stored. On further calls, the stored
73      * value is returned.
74      *
75      * @return the expected indentation amount
76      */

77     public final IndentLevel getLevel()
78     {
79         if (mLevel == null) {
80             mLevel = getLevelImpl();
81         }
82         return mLevel;
83     }
84
85     /**
86      * Compute the indentation amount for this handler.
87      *
88      * @return the expected indentation amount
89      */

90     protected IndentLevel getLevelImpl()
91     {
92         return mParent.suggestedChildLevel(this);
93     }
94
95     /**
96      * Indentation level suggested for a child element. Children don't have
97      * to respect this, but most do.
98      *
99      * @param aChild child AST (so suggestion level can differ based on child
100      * type)
101      *
102      * @return suggested indentation for child
103      */

104     public IndentLevel suggestedChildLevel(ExpressionHandler aChild)
105     {
106         return new IndentLevel(getLevel(), getBasicOffset());
107     }
108
109     /**
110      * Log an indentation error.
111      *
112      * @param aAst the expression that caused the error
113      * @param aSubtypeName the type of the expression
114      * @param aActualLevel the actual indent level of the expression
115      */

116     protected final void logError(DetailAST aAst, String JavaDoc aSubtypeName,
117                                   int aActualLevel)
118     {
119         logError(aAst, aSubtypeName, aActualLevel, getLevel());
120     }
121
122     /**
123      * Log an indentation error.
124      *
125      * @param aAst the expression that caused the error
126      * @param aSubtypeName the type of the expression
127      * @param aActualLevel the actual indent level of the expression
128      * @param aExpectedLevel the expected indent level of the expression
129      */

130     protected final void logError(DetailAST aAst, String JavaDoc aSubtypeName,
131                                   int aActualLevel, IndentLevel aExpectedLevel)
132     {
133         final String JavaDoc typeStr =
134             ("".equals(aSubtypeName) ? "" : (" " + aSubtypeName));
135         final Object JavaDoc[] args = new Object JavaDoc[] {
136             mTypeName + typeStr,
137             new Integer JavaDoc(aActualLevel),
138             aExpectedLevel,
139         };
140         mIndentCheck.indentationLog(aAst.getLineNo(),
141                                     "indentation.error",
142                                     args);
143     }
144
145     /**
146      * Log child indentation error.
147      *
148      * @param aLine the expression that caused the error
149      * @param aActualLevel the actual indent level of the expression
150      * @param aExpectedLevel the expected indent level of the expression
151      */

152     private void logChildError(int aLine,
153                                int aActualLevel,
154                                IndentLevel aExpectedLevel)
155     {
156         final Object JavaDoc[] args = new Object JavaDoc[] {
157             mTypeName,
158             new Integer JavaDoc(aActualLevel),
159             aExpectedLevel,
160         };
161         mIndentCheck.indentationLog(aLine,
162                                     "indentation.child.error",
163                                     args);
164     }
165
166     /**
167      * Determines if the given expression is at the start of a line.
168      *
169      * @param aAst the expression to check
170      *
171      * @return true if it is, false otherwise
172      */

173     protected final boolean startsLine(DetailAST aAst)
174     {
175         return getLineStart(aAst) == expandedTabsColumnNo(aAst);
176     }
177
178     /**
179      * Determines if two expressions are on the same line.
180      *
181      * @param aAst1 the first expression
182      * @param aAst2 the second expression
183      *
184      * @return true if they are, false otherwise
185      */

186     static boolean areOnSameLine(DetailAST aAst1, DetailAST aAst2)
187     {
188         return (aAst1 != null) && (aAst2 != null)
189             && (aAst1.getLineNo() == aAst2.getLineNo());
190     }
191
192     /**
193      * Searchs in given sub-tree (including given node) for the token
194      * which represents first symbol for this sub-tree in file.
195      * @param aAST a root of sub-tree in which the search shoul be performed.
196      * @return a token which occurs first in the file.
197      */

198     static DetailAST getFirstToken(DetailAST aAST)
199     {
200         DetailAST first = aAST;
201         DetailAST child = (DetailAST) aAST.getFirstChild();
202
203         while (child != null) {
204             final DetailAST toTest = getFirstToken(child);
205             if ((toTest.getLineNo() < first.getLineNo())
206                 || ((toTest.getLineNo() == first.getLineNo())
207                     && (toTest.getColumnNo() < first.getColumnNo())))
208             {
209                 first = toTest;
210             }
211             child = (DetailAST) child.getNextSibling();
212         }
213
214         return first;
215     }
216
217     /**
218      * Get the start of the line for the given expression.
219      *
220      * @param aAst the expression to find the start of the line for
221      *
222      * @return the start of the line for the given expression
223      */

224     protected final int getLineStart(DetailAST aAst)
225     {
226         final String JavaDoc line = mIndentCheck.getLines()[aAst.getLineNo() - 1];
227         return getLineStart(line);
228     }
229
230     // TODO: this whole checking of consecuitive/expression line indents is
231
// smelling pretty bad... and is in serious need of pruning. But, I
232
// want to finish the invalid tests before I start messing around with
233
// it.
234

235     /**
236      * Check the indentation of consecutive lines for the expression we are
237      * handling.
238      *
239      * @param aStartLine the first line to check
240      * @param aEndLine the last line to check
241      * @param aIndentLevel the required indent level
242      */

243     protected final void checkLinesIndent(int aStartLine, int aEndLine,
244                                           IndentLevel aIndentLevel)
245     {
246         // check first line
247
checkSingleLine(aStartLine, aIndentLevel);
248
249         // check following lines
250
aIndentLevel = new IndentLevel(aIndentLevel, getBasicOffset());
251         for (int i = aStartLine + 1; i <= aEndLine; i++) {
252             checkSingleLine(i, aIndentLevel);
253         }
254     }
255
256     /**
257      * @return true if indentation should be increased after
258      * fisrt line in checkLinesIndent()
259      * false otherwise
260      */

261     protected boolean shouldIncreaseIndent()
262     {
263         return true;
264     }
265
266     /**
267      * Check the indentation for a set of lines.
268      *
269      * @param aLines the set of lines to check
270      * @param aIndentLevel the indentation level
271      * @param aFirstLineMatches whether or not the first line has to match
272      * @param aFirstLine firstline of whole expression
273      */

274     private void checkLinesIndent(LineSet aLines,
275                                   IndentLevel aIndentLevel,
276                                   boolean aFirstLineMatches,
277                                   int aFirstLine)
278     {
279         if (aLines.isEmpty()) {
280             return;
281         }
282
283         // check first line
284
final int startLine = aLines.firstLine();
285         final int endLine = aLines.lastLine();
286         final int startCol = aLines.firstLineCol();
287
288         final int realStartCol =
289             getLineStart(mIndentCheck.getLines()[startLine - 1]);
290
291         if (realStartCol == startCol) {
292             checkSingleLine(startLine, startCol, aIndentLevel,
293                 aFirstLineMatches);
294         }
295
296         // if first line starts the line, following lines are indented
297
// one level; but if the first line of this expression is
298
// nested with the previous expression (which is assumed if it
299
// doesn't start the line) then don't indent more, the first
300
// indentation is absorbed by the nesting
301

302         // TODO: shouldIncreaseIndent() is a hack, should be removed
303
// after complete rewriting of checkExpressionSubtree()
304

305         if (aFirstLineMatches
306             || ((aFirstLine > mMainAst.getLineNo()) && shouldIncreaseIndent()))
307         {
308             aIndentLevel = new IndentLevel(aIndentLevel, getBasicOffset());
309         }
310
311         // check following lines
312
for (int i = startLine + 1; i <= endLine; i++) {
313             final Integer JavaDoc col = aLines.getStartColumn(new Integer JavaDoc(i));
314             // startCol could be null if this line didn't have an
315
// expression that was required to be checked (it could be
316
// checked by a child expression)
317

318             if (col != null) {
319                 checkSingleLine(i, col.intValue(), aIndentLevel, false);
320             }
321         }
322     }
323
324     /**
325      * Check the indent level for a single line.
326      *
327      * @param aLineNum the line number to check
328      * @param aIndentLevel the required indent level
329      */

330     private void checkSingleLine(int aLineNum, IndentLevel aIndentLevel)
331     {
332         final String JavaDoc line = mIndentCheck.getLines()[aLineNum - 1];
333         final int start = getLineStart(line);
334         if (aIndentLevel.gt(start)) {
335             logChildError(aLineNum, start, aIndentLevel);
336         }
337     }
338
339     /**
340      * Check the indentation for a single line.
341      *
342      * @param aLineNum the number of the line to check
343      * @param aColNum the column number we are starting at
344      * @param aIndentLevel the indentation level
345      * @param aMustMatch whether or not the indentation level must match
346      */

347
348     private void checkSingleLine(int aLineNum, int aColNum,
349         IndentLevel aIndentLevel, boolean aMustMatch)
350     {
351         final String JavaDoc line = mIndentCheck.getLines()[aLineNum - 1];
352         final int start = getLineStart(line);
353         // if must match is set, it is an error if the line start is not
354
// at the correct indention level; otherwise, it is an only an
355
// error if this statement starts the line and it is less than
356
// the correct indentation level
357
if (aMustMatch ? !aIndentLevel.accept(start)
358             : (aColNum == start) && aIndentLevel.gt(start))
359         {
360             logChildError(aLineNum, start, aIndentLevel);
361         }
362     }
363
364     /**
365      * Get the start of the specified line.
366      *
367      * @param aLine the specified line number
368      *
369      * @return the start of the specified line
370      */

371     protected final int getLineStart(String JavaDoc aLine)
372     {
373         for (int start = 0; start < aLine.length(); start++) {
374             final char c = aLine.charAt(start);
375
376             if (!Character.isWhitespace(c)) {
377                 return Utils.lengthExpandedTabs(
378                     aLine, start, mIndentCheck.getIndentationTabWidth());
379             }
380         }
381         return 0;
382     }
383
384     // TODO: allowNesting either shouldn't be allowed with
385
// firstLineMatches, or I should change the firstLineMatches logic
386
// so it doesn't match if the first line is nested
387

388     /**
389      * Check the indent level of the children of the specified parent
390      * expression.
391      *
392      * @param aParent the parent whose children we are checking
393      * @param aTokenTypes the token types to check
394      * @param aStartLevel the starting indent level
395      * @param aFirstLineMatches whether or not the first line needs to match
396      * @param aAllowNesting whether or not nested children are allowed
397      */

398     protected final void checkChildren(DetailAST aParent,
399                                        int[] aTokenTypes,
400                                        IndentLevel aStartLevel,
401                                        boolean aFirstLineMatches,
402                                        boolean aAllowNesting)
403     {
404         Arrays.sort(aTokenTypes);
405         for (DetailAST child = (DetailAST) aParent.getFirstChild();
406                 child != null;
407                 child = (DetailAST) child.getNextSibling())
408         {
409             if (Arrays.binarySearch(aTokenTypes, child.getType()) >= 0) {
410                 checkExpressionSubtree(child, aStartLevel,
411                     aFirstLineMatches, aAllowNesting);
412             }
413         }
414     }
415
416     /**
417      * Check the indentation level for an expression subtree.
418      *
419      * @param aTree the expression subtree to check
420      * @param aLevel the indentation level
421      * @param aFirstLineMatches whether or not the first line has to match
422      * @param aAllowNesting whether or not subtree nesting is allowed
423      */

424     protected final void checkExpressionSubtree(
425         DetailAST aTree,
426         IndentLevel aLevel,
427         boolean aFirstLineMatches,
428         boolean aAllowNesting
429     )
430     {
431         final LineSet subtreeLines = new LineSet();
432         final int firstLine = getFirstLine(Integer.MAX_VALUE, aTree);
433         if (aFirstLineMatches && !aAllowNesting) {
434             subtreeLines.addLineAndCol(new Integer JavaDoc(firstLine),
435                 getLineStart(mIndentCheck.getLines()[firstLine - 1]));
436         }
437         findSubtreeLines(subtreeLines, aTree, aAllowNesting);
438
439         checkLinesIndent(subtreeLines, aLevel, aFirstLineMatches, firstLine);
440     }
441
442     /**
443      * Get the first line for a given expression.
444      *
445      * @param aStartLine the line we are starting from
446      * @param aTree the expression to find the first line for
447      *
448      * @return the first line of the expression
449      */

450     protected final int getFirstLine(int aStartLine, DetailAST aTree)
451     {
452         // find line for this node
453
// TODO: getLineNo should probably not return < 0, but it is for
454
// the interface methods... I should ask about this
455

456         final int currLine = aTree.getLineNo();
457         if (currLine < aStartLine) {
458             aStartLine = currLine;
459         }
460
461         // check children
462
for (DetailAST node = (DetailAST) aTree.getFirstChild();
463             node != null;
464             node = (DetailAST) node.getNextSibling())
465         {
466             aStartLine = getFirstLine(aStartLine, node);
467         }
468
469         return aStartLine;
470     }
471
472     /**
473      * Get the column number for the start of a given expression, expanding
474      * tabs out into spaces in the process.
475      *
476      * @param aAST the expression to find the start of
477      *
478      * @return the column number for the start of the expression
479      */

480     protected final int expandedTabsColumnNo(DetailAST aAST)
481     {
482         final String JavaDoc line =
483             mIndentCheck.getLines()[aAST.getLineNo() - 1];
484
485         return Utils.lengthExpandedTabs(line, aAST.getColumnNo(),
486             mIndentCheck.getIndentationTabWidth());
487     }
488
489     /**
490      * Find the set of lines for a given subtree.
491      *
492      * @param aLines the set of lines to add to
493      * @param aTree the subtree to examine
494      * @param aAllowNesting whether or not to allow nested subtrees
495      */

496     protected final void findSubtreeLines(LineSet aLines, DetailAST aTree,
497         boolean aAllowNesting)
498     {
499         // find line for this node
500
// TODO: getLineNo should probably not return < 0, but it is for
501
// the interface methods... I should ask about this
502

503         if (getIndentCheck().getHandlerFactory().isHandledType(aTree.getType())
504             || (aTree.getLineNo() < 0))
505         {
506             return;
507         }
508
509         // TODO: the problem with this is that not all tree tokens actually
510
// have the right column number -- I should get a list of these
511
// and verify that checking nesting this way won't cause problems
512
// if (aAllowNesting && aTree.getColumnNo() != getLineStart(aTree)) {
513
// return;
514
// }
515

516         final Integer JavaDoc lineNum = new Integer JavaDoc(aTree.getLineNo());
517         final Integer JavaDoc colNum = aLines.getStartColumn(lineNum);
518
519         final int thisLineColumn = expandedTabsColumnNo(aTree);
520         if ((colNum == null) || (thisLineColumn < colNum.intValue())) {
521             aLines.addLineAndCol(lineNum, thisLineColumn);
522         }
523
524         // check children
525
for (DetailAST node = (DetailAST) aTree.getFirstChild();
526             node != null;
527             node = (DetailAST) node.getNextSibling())
528         {
529             findSubtreeLines(aLines, node, aAllowNesting);
530         }
531     }
532
533     /**
534      * Check the indentation level of modifiers.
535      */

536     protected final void checkModifiers()
537     {
538         final DetailAST modifiers =
539             mMainAst.findFirstToken(TokenTypes.MODIFIERS);
540         for (DetailAST modifier = (DetailAST) modifiers.getFirstChild();
541              modifier != null;
542              modifier = (DetailAST) modifier.getNextSibling())
543         {
544             /*
545             if (!areOnSameLine(modifier, prevExpr)) {
546                 continue;
547             }
548             */

549             if (startsLine(modifier)
550                 && !getLevel().accept(expandedTabsColumnNo(modifier)))
551             {
552                 logError(modifier, "modifier",
553                     expandedTabsColumnNo(modifier));
554             }
555         }
556     }
557
558     /**
559      * Check the indentation of the expression we are handling.
560      */

561     public abstract void checkIndentation();
562
563     /**
564      * Accessor for the IndentCheck attribute.
565      *
566      * @return the IndentCheck attribute
567      */

568     protected final IndentationCheck getIndentCheck()
569     {
570         return mIndentCheck;
571     }
572
573     /**
574      * Accessor for the MainAst attribute.
575      *
576      * @return the MainAst attribute
577      */

578     protected final DetailAST getMainAst()
579     {
580         return mMainAst;
581     }
582
583     /**
584      * Accessor for the Parent attribute.
585      *
586      * @return the Parent attribute
587      */

588     protected final ExpressionHandler getParent()
589     {
590         return mParent;
591     }
592
593     /**
594      * A shortcut for <code>IndentationCheck</code> property.
595      * @return value of basicOffset property of <code>IndentationCheck</code>
596      */

597     protected final int getBasicOffset()
598     {
599         return getIndentCheck().getBasicOffset();
600     }
601
602     /**
603      * A shortcut for <code>IndentationCheck</code> property.
604      * @return value of braceAdjustment property
605      * of <code>IndentationCheck</code>
606      */

607     protected final int getBraceAdjustement()
608     {
609         return getIndentCheck().getBraceAdjustement();
610     }
611
612     /**
613      * Check the indentation of the right parenthesis.
614      * @param aRparen parenthesis to check
615      * @param aLparen left parenthesis associated with aRparen
616      */

617     protected final void checkRParen(DetailAST aLparen, DetailAST aRparen)
618     {
619         // no paren - no check :)
620
if (aRparen == null) {
621             return;
622         }
623
624         // the rcurly can either be at the correct indentation,
625
// or not first on the line ...
626
final int rparenLevel = expandedTabsColumnNo(aRparen);
627         if (getLevel().accept(rparenLevel) || !startsLine(aRparen)) {
628             return;
629         }
630
631         // or has <lparen level> + 1 indentation
632
final int lparenLevel = expandedTabsColumnNo(aLparen);
633         if (rparenLevel == (lparenLevel + 1)) {
634             return;
635         }
636
637         logError(aRparen, "rparen", rparenLevel);
638     }
639
640     /**
641      * Check the indentation of the left parenthesis.
642      * @param aLparen parenthesis to check
643      */

644     protected final void checkLParen(final DetailAST aLparen)
645     {
646         // the rcurly can either be at the correct indentation, or on the
647
// same line as the lcurly
648
if ((aLparen == null)
649             || getLevel().accept(expandedTabsColumnNo(aLparen))
650             || !startsLine(aLparen))
651         {
652             return;
653         }
654         logError(aLparen, "lparen", expandedTabsColumnNo(aLparen));
655     }
656 }
657
Popular Tags