KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > puppycrawl > tools > checkstyle > checks > coding > FallThroughCheck


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.coding;
20
21 import java.util.regex.Matcher JavaDoc;
22 import java.util.regex.Pattern JavaDoc;
23
24 import com.puppycrawl.tools.checkstyle.api.Check;
25 import com.puppycrawl.tools.checkstyle.api.DetailAST;
26 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27 import com.puppycrawl.tools.checkstyle.api.Utils;
28
29 /**
30  * Checks for fall through in switch statements
31  * Finds locations where a case contains Java code -
32  * but lacks a break, return, throw or continue statement.
33  *
34  * <p>
35  * The check honors special comments to suppress warnings about
36  * the fall through. By default the comments "fallthru",
37  * "fall through", "falls through" and "fallthrough" are recognized.
38  * </p>
39  * <p>
40  * The following fragment of code will NOT trigger the check,
41  * because of the comment "fallthru".
42  * </p>
43  * <pre>
44  * case 3:
45  * x = 2;
46  * // fallthru
47  * case 4:
48  * </pre>
49  * <p>
50  * The recognized relief comment can be configured with the property
51  * <code>reliefPattern</code>. Default value of this regular expression
52  * is "fallthru|fall through|fallthrough|falls through".
53  * </p>
54  * <p>
55  * An example of how to configure the check is:
56  * </p>
57  * <pre>
58  * &lt;module name="FallThrough"&gt;
59  * &lt;property name=&quot;reliefPattern&quot;
60  * value=&quot;Fall Through&quot;/&gt;
61  * &lt;/module&gt;
62  * </pre>
63  *
64  * @author o_sukhodolsky
65  */

66 public class FallThroughCheck extends Check
67 {
68     /** Do we need to check last case group. */
69     private boolean mCheckLastGroup;
70
71     /** Relief pattern to allow fall throught to the next case branch. */
72     private String JavaDoc mReliefPattern = "fallthru|falls? ?through";
73
74     /** Relief regexp. */
75     private Pattern JavaDoc mRegExp;
76
77     /** Creates new instance of the check. */
78     public FallThroughCheck()
79     {
80         // do nothing
81
}
82
83     /** {@inheritDoc} */
84     public int[] getDefaultTokens()
85     {
86         return new int[]{TokenTypes.CASE_GROUP};
87     }
88
89     /** {@inheritDoc} */
90     public int[] getRequiredTokens()
91     {
92         return getDefaultTokens();
93     }
94
95     /**
96      * Set the relief pattern.
97      *
98      * @param aPattern
99      * The regular expression pattern.
100      */

101     public void setReliefPattern(String JavaDoc aPattern)
102     {
103         mReliefPattern = aPattern;
104     }
105
106     /**
107      * Configures whether we need to check last case group or not.
108      * @param aValue new value of the property.
109      */

110     public void setCheckLastCaseGroup(boolean aValue)
111     {
112         mCheckLastGroup = aValue;
113     }
114
115     /** {@inheritDoc} */
116     public void init()
117     {
118         super.init();
119         mRegExp = Utils.getPattern(mReliefPattern);
120     }
121
122     /** {@inheritDoc} */
123     public void visitToken(DetailAST aAST)
124     {
125         final DetailAST nextGroup = (DetailAST) aAST.getNextSibling();
126         final boolean isLastGroup =
127             ((nextGroup == null)
128              || (nextGroup.getType() != TokenTypes.CASE_GROUP));
129         if (isLastGroup && !mCheckLastGroup) {
130             // we do not need to check last group
131
return;
132         }
133
134         final DetailAST slist = aAST.findFirstToken(TokenTypes.SLIST);
135
136         if (!isTerminated(slist, true, true)) {
137             if (!hasFallTruComment(aAST, nextGroup)) {
138                 if (!isLastGroup) {
139                     log(nextGroup, "fall.through");
140                 }
141                 else {
142                     log(aAST, "fall.through.last");
143                 }
144             }
145         }
146     }
147
148     /**
149      * Checks if a given subtree terminated by return, throw or,
150      * if allowed break, continue.
151      * @param aAST root of given subtree
152      * @param aUseBreak should we consider break as terminator.
153      * @param aUseContinue should we consider continue as terminator.
154      * @return true if the subtree is terminated.
155      */

156     private boolean isTerminated(final DetailAST aAST, boolean aUseBreak,
157                                  boolean aUseContinue)
158     {
159         switch (aAST.getType()) {
160         case TokenTypes.LITERAL_RETURN:
161         case TokenTypes.LITERAL_THROW:
162             return true;
163         case TokenTypes.LITERAL_BREAK:
164             return aUseBreak;
165         case TokenTypes.LITERAL_CONTINUE:
166             return aUseContinue;
167         case TokenTypes.SLIST:
168             return checkSlist(aAST, aUseBreak, aUseContinue);
169         case TokenTypes.LITERAL_IF:
170             return checkIf(aAST, aUseBreak, aUseContinue);
171         case TokenTypes.LITERAL_FOR:
172         case TokenTypes.LITERAL_WHILE:
173         case TokenTypes.LITERAL_DO:
174             return checkLoop(aAST);
175         case TokenTypes.LITERAL_TRY:
176             return checkTry(aAST, aUseBreak, aUseContinue);
177         case TokenTypes.LITERAL_SWITCH:
178             return checkSwitch(aAST, aUseContinue);
179         default:
180             return false;
181         }
182     }
183
184     /**
185      * Checks if a given SLIST terminated by return, throw or,
186      * if allowed break, continue.
187      * @param aAST SLIST to check
188      * @param aUseBreak should we consider break as terminator.
189      * @param aUseContinue should we consider continue as terminator.
190      * @return true if SLIST is terminated.
191      */

192     private boolean checkSlist(final DetailAST aAST, boolean aUseBreak,
193                                boolean aUseContinue)
194     {
195         DetailAST lastStmt = aAST.getLastChild();
196
197         if (lastStmt.getType() == TokenTypes.RCURLY) {
198             lastStmt = lastStmt.getPreviousSibling();
199         }
200
201         return (lastStmt != null)
202             && isTerminated(lastStmt, aUseBreak, aUseContinue);
203     }
204
205     /**
206      * Checks if a given IF terminated by return, throw or,
207      * if allowed break, continue.
208      * @param aAST IF to check
209      * @param aUseBreak should we consider break as terminator.
210      * @param aUseContinue should we consider continue as terminator.
211      * @return true if IF is terminated.
212      */

213     private boolean checkIf(final DetailAST aAST, boolean aUseBreak,
214                             boolean aUseContinue)
215     {
216         final DetailAST thenStmt = (DetailAST)
217             aAST.findFirstToken(TokenTypes.RPAREN).getNextSibling();
218         final DetailAST elseStmt = (DetailAST) thenStmt.getNextSibling();
219         boolean isTerminated = isTerminated(thenStmt, aUseBreak, aUseContinue);
220
221         if (isTerminated && (elseStmt != null)) {
222             isTerminated = isTerminated((DetailAST) elseStmt.getFirstChild(),
223                                         aUseBreak, aUseContinue);
224         }
225         return isTerminated;
226     }
227
228     /**
229      * Checks if a given loop terminated by return, throw or,
230      * if allowed break, continue.
231      * @param aAST loop to check
232      * @return true if loop is terminated.
233      */

234     private boolean checkLoop(final DetailAST aAST)
235     {
236         DetailAST loopBody = null;
237         if (aAST.getType() == TokenTypes.LITERAL_DO) {
238             final DetailAST lparen = aAST.findFirstToken(TokenTypes.DO_WHILE);
239             loopBody = lparen.getPreviousSibling();
240         }
241         else {
242             final DetailAST rparen = aAST.findFirstToken(TokenTypes.RPAREN);
243             loopBody = (DetailAST) rparen.getNextSibling();
244         }
245         return isTerminated(loopBody, false, false);
246     }
247
248     /**
249      * Checks if a given try/catch/finally block terminated by return, throw or,
250      * if allowed break, continue.
251      * @param aAST loop to check
252      * @param aUseBreak should we consider break as terminator.
253      * @param aUseContinue should we consider continue as terminator.
254      * @return true if try/cath/finally block is terminated.
255      */

256     private boolean checkTry(final DetailAST aAST, boolean aUseBreak,
257                              boolean aUseContinue)
258     {
259         final DetailAST finalStmt = aAST.getLastChild();
260         if (finalStmt.getType() == TokenTypes.LITERAL_FINALLY) {
261             return isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
262                                 aUseBreak, aUseContinue);
263         }
264
265         boolean isTerminated = isTerminated((DetailAST) aAST.getFirstChild(),
266                                             aUseBreak, aUseContinue);
267
268         DetailAST catchStmt = aAST.findFirstToken(TokenTypes.LITERAL_CATCH);
269         while ((catchStmt != null) && isTerminated) {
270             final DetailAST catchBody =
271                 catchStmt.findFirstToken(TokenTypes.SLIST);
272             isTerminated &= isTerminated(catchBody, aUseBreak, aUseContinue);
273             catchStmt = (DetailAST) catchStmt.getNextSibling();
274         }
275         return isTerminated;
276     }
277
278     /**
279      * Checks if a given switch terminated by return, throw or,
280      * if allowed break, continue.
281      * @param aAST loop to check
282      * @param aUseContinue should we consider continue as terminator.
283      * @return true if switch is terminated.
284      */

285     private boolean checkSwitch(final DetailAST aAST, boolean aUseContinue)
286     {
287         DetailAST caseGroup = aAST.findFirstToken(TokenTypes.CASE_GROUP);
288         boolean isTerminated = (caseGroup != null);
289         while (isTerminated && (caseGroup != null)
290                && (caseGroup.getType() != TokenTypes.RCURLY))
291         {
292             final DetailAST caseBody =
293                 caseGroup.findFirstToken(TokenTypes.SLIST);
294             isTerminated &= isTerminated(caseBody, false, aUseContinue);
295             caseGroup = (DetailAST) caseGroup.getNextSibling();
296         }
297         return isTerminated;
298     }
299
300     /**
301      * Determines if the fall through case between <code>aCurrentCase</code> and
302      * <code>aNextCase</code> is reliefed by a appropriate comment.
303      *
304      * @param aCurrentCase AST of the case that falls through to the next case.
305      * @param aNextCase AST of the next case.
306      * @return True if a relief comment was found
307      */

308     private boolean hasFallTruComment(DetailAST aCurrentCase,
309             DetailAST aNextCase)
310     {
311
312         final int startLineNo = aCurrentCase.getLineNo();
313         final int endLineNo = aNextCase.getLineNo();
314         final int endColNo = aNextCase.getColumnNo();
315
316         /*
317          * Remember: The lines number returned from the AST is 1-based, but
318          * the lines number in this array are 0-based. So you will often
319          * see a "lineNo-1" etc.
320          */

321         final String JavaDoc[] lines = getLines();
322
323         /*
324          * Handle:
325          * case 1:
326          * /+ FALLTHRU +/ case 2:
327          * ....
328          * and
329          * switch(i) {
330          * default:
331          * /+ FALLTHRU +/}
332          */

333         final String JavaDoc linepart = lines[endLineNo - 1].substring(0, endColNo);
334         if (commentMatch(mRegExp, linepart, endLineNo)) {
335             return true;
336         }
337
338         /*
339          * Handle:
340          * case 1:
341          * .....
342          * // FALLTHRU
343          * case 2:
344          * ....
345          * and
346          * switch(i) {
347          * default:
348          * // FALLTHRU
349          * }
350          */

351         for (int i = endLineNo - 2; i > startLineNo - 1; i--) {
352             if (lines[i].trim().length() != 0) {
353                 return commentMatch(mRegExp, lines[i], i + 1);
354             }
355         }
356
357         // Well -- no relief comment found.
358
return false;
359     }
360
361     /**
362      * Does a regular expression match on the given line and checks that a
363      * possible match is within a comment.
364      * @param aPattern The regular expression pattern to use.
365      * @param aLine The line of test to do the match on.
366      * @param aLineNo The line number in the file.
367      * @return True if a match was found inside a comment.
368      */

369     private boolean commentMatch(Pattern JavaDoc aPattern, String JavaDoc aLine, int aLineNo
370     )
371     {
372         final Matcher JavaDoc matcher = aPattern.matcher(aLine);
373
374         final boolean hit = matcher.find();
375
376         if (hit) {
377             final int startMatch = matcher.start();
378             // -1 because it returns the char position beyond the match
379
final int endMatch = matcher.end() - 1;
380             return getFileContents().hasIntersectionWithComment(aLineNo,
381                     startMatch, aLineNo, endMatch);
382         }
383         return false;
384     }
385 }
386
Popular Tags