KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > puppycrawl > tools > checkstyle > checks > javadoc > JavadocStyleCheck


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.javadoc;
20
21 import java.util.Stack JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.regex.Pattern JavaDoc;
24 import java.util.regex.PatternSyntaxException JavaDoc;
25
26 import com.puppycrawl.tools.checkstyle.api.Check;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.FileContents;
29 import com.puppycrawl.tools.checkstyle.api.Scope;
30 import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
31 import com.puppycrawl.tools.checkstyle.api.TextBlock;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
34
35 /**
36  * Custom Checkstyle Check to validate Javadoc.
37  *
38  * @author Chris Stillwell
39  * @author Daniel Grenner
40  * @version 1.2
41  */

42 public class JavadocStyleCheck
43     extends Check
44 {
45     /** Message property key for the Unclosed HTML message. */
46     private static final String JavaDoc UNCLOSED_HTML = "javadoc.unclosedhtml";
47
48     /** Message property key for the Extra HTML message. */
49     private static final String JavaDoc EXTRA_HTML = "javadoc.extrahtml";
50
51     /** HTML tags that do not require a close tag. */
52     private static final String JavaDoc[] SINGLE_TAG =
53     {"p", "br", "li", "dt", "dd", "td", "hr", "img", "tr", "th", "td"};
54
55     /** The scope to check. */
56     private Scope mScope = Scope.PRIVATE;
57
58     /** the visibility scope where Javadoc comments shouldn't be checked **/
59     private Scope mExcludeScope;
60
61     /** Regular expression for matching the end of a sentence. */
62     private Pattern JavaDoc mEndOfSentencePattern;
63
64     /**
65      * Indicates if the first sentence should be checked for proper end of
66      * sentence punctuation.
67      */

68     private boolean mCheckFirstSentence = true;
69
70     /**
71      * Indicates if the HTML within the comment should be checked.
72      */

73     private boolean mCheckHtml = true;
74
75     /**
76      * Indicates if empty javadoc statements should be checked.
77      */

78     private boolean mCheckEmptyJavadoc;
79
80     /** {@inheritDoc} */
81     public int[] getDefaultTokens()
82     {
83         return new int[] {
84             TokenTypes.INTERFACE_DEF,
85             TokenTypes.CLASS_DEF,
86             TokenTypes.ANNOTATION_DEF,
87             TokenTypes.ENUM_DEF,
88             TokenTypes.METHOD_DEF,
89             TokenTypes.CTOR_DEF,
90             TokenTypes.VARIABLE_DEF,
91             TokenTypes.ENUM_CONSTANT_DEF,
92             TokenTypes.ANNOTATION_FIELD_DEF,
93         };
94     }
95
96     /** {@inheritDoc} */
97     public void visitToken(DetailAST aAST)
98     {
99         if (shouldCheck(aAST)) {
100             final FileContents contents = getFileContents();
101             final TextBlock cmt =
102                 contents.getJavadocBefore(aAST.getLineNo());
103
104             checkComment(aAST, cmt);
105         }
106     }
107
108     /**
109      * Whether we should check this node.
110      * @param aAST a given node.
111      * @return whether we should check a given node.
112      */

113     private boolean shouldCheck(final DetailAST aAST)
114     {
115         if (ScopeUtils.inCodeBlock(aAST)) {
116             return false;
117         }
118
119         final Scope declaredScope;
120         if (aAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
121             declaredScope = Scope.PUBLIC;
122         }
123         else {
124             declaredScope = ScopeUtils.getScopeFromMods(
125                 aAST.findFirstToken(TokenTypes.MODIFIERS));
126         }
127
128         final Scope scope =
129             ScopeUtils.inInterfaceOrAnnotationBlock(aAST)
130             ? Scope.PUBLIC : declaredScope;
131         final Scope surroundingScope = ScopeUtils.getSurroundingScope(aAST);
132
133         return scope.isIn(mScope)
134             && ((surroundingScope == null) || surroundingScope.isIn(mScope))
135             && ((mExcludeScope == null)
136                 || !scope.isIn(mExcludeScope)
137                 || ((surroundingScope != null)
138                 && !surroundingScope.isIn(mExcludeScope)));
139     }
140
141     /**
142      * Performs the various checks agains the Javadoc comment.
143      *
144      * @param aAST the AST of the element being documented
145      * @param aComment the source lines that make up the Javadoc comment.
146      *
147      * @see #checkFirstSentence(TextBlock)
148      * @see #checkHtml(DetailAST, TextBlock)
149      */

150     private void checkComment(final DetailAST aAST, final TextBlock aComment)
151     {
152         if (aComment == null) {
153             return;
154         }
155
156         if (mCheckFirstSentence) {
157             checkFirstSentence(aComment);
158         }
159
160         if (mCheckHtml) {
161             checkHtml(aAST, aComment);
162         }
163
164         if (mCheckEmptyJavadoc) {
165             checkEmptyJavadoc(aComment);
166         }
167     }
168
169     /**
170      * Checks that the first sentence ends with proper puctuation. This method
171      * uses a regular expression that checks for the presence of a period,
172      * question mark, or exclaimation mark followed either by whitespace, an
173      * HTML element, or the end of string. This method ignores {_AT_inheritDoc}
174      * comments.
175      *
176      * @param aComment the source lines that make up the Javadoc comment.
177      */

178     private void checkFirstSentence(TextBlock aComment)
179     {
180         final String JavaDoc commentText = getCommentText(aComment.getText());
181
182         if ((commentText.length() != 0)
183             && !getEndOfSentencePattern().matcher(commentText).find()
184             && !"{@inheritDoc}".equals(commentText))
185         {
186             log(aComment.getStartLineNo(), "javadoc.noperiod");
187         }
188     }
189
190     /**
191      * Checks that the Javadoc is not empty.
192      *
193      * @param aComment the source lines that make up the Javadoc comment.
194      */

195     private void checkEmptyJavadoc(TextBlock aComment)
196     {
197         final String JavaDoc commentText = getCommentText(aComment.getText());
198
199         if (commentText.length() == 0) {
200             log(aComment.getStartLineNo(), "javadoc.empty");
201         }
202     }
203
204     /**
205      * Returns the comment text from the Javadoc.
206      * @param aComments the lines of Javadoc.
207      * @return a comment text String.
208      */

209     private String JavaDoc getCommentText(String JavaDoc[] aComments)
210     {
211         final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
212         for (int i = 0; i < aComments.length; i++) {
213             final String JavaDoc line = aComments[i];
214             final int textStart = findTextStart(line);
215
216             if (textStart != -1) {
217                 if (line.charAt(textStart) == '@') {
218                     //we have found the tag section
219
break;
220                 }
221                 buffer.append(line.substring(textStart));
222                 trimTail(buffer);
223                 buffer.append('\n');
224             }
225         }
226
227         return buffer.toString().trim();
228     }
229
230     /**
231      * Finds the index of the first non-whitespace character ignoring the
232      * Javadoc comment start and end strings (&#47** and *&#47) as well as any
233      * leading asterisk.
234      * @param aLine the Javadoc comment line of text to scan.
235      * @return the int index relative to 0 for the start of text
236      * or -1 if not found.
237      */

238     private int findTextStart(String JavaDoc aLine)
239     {
240         int textStart = -1;
241         for (int i = 0; i < aLine.length(); i++) {
242             if (!Character.isWhitespace(aLine.charAt(i))) {
243                 if (aLine.regionMatches(i, "/**", 0, "/**".length())) {
244                     i += 2;
245                 }
246                 else if (aLine.regionMatches(i, "*/", 0, 2)) {
247                     i++;
248                 }
249                 else if (aLine.charAt(i) != '*') {
250                     textStart = i;
251                     break;
252                 }
253             }
254         }
255         return textStart;
256     }
257
258     /**
259      * Trims any trailing whitespace or the end of Javadoc comment string.
260      * @param aBuffer the StringBuffer to trim.
261      */

262     private void trimTail(StringBuffer JavaDoc aBuffer)
263     {
264         for (int i = aBuffer.length() - 1; i >= 0; i--) {
265             if (Character.isWhitespace(aBuffer.charAt(i))) {
266                 aBuffer.deleteCharAt(i);
267             }
268             else if ((i > 0)
269                      && (aBuffer.charAt(i - 1) == '*')
270                      && (aBuffer.charAt(i) == '/'))
271             {
272                 aBuffer.deleteCharAt(i);
273                 aBuffer.deleteCharAt(i - 1);
274                 i--;
275                 while (aBuffer.charAt(i - 1) == '*') {
276                     aBuffer.deleteCharAt(i - 1);
277                     i--;
278                 }
279             }
280             else {
281                 break;
282             }
283         }
284     }
285
286     /**
287      * Checks the comment for HTML tags that do not have a corresponding close
288      * tag or a close tage that has no previous open tag. This code was
289      * primarily copied from the DocCheck checkHtml method.
290      *
291      * @param aAST the node with the Javadoc
292      * @param aComment the <code>TextBlock</code> which represents
293      * the Javadoc comment.
294      */

295     private void checkHtml(final DetailAST aAST, final TextBlock aComment)
296     {
297         final int lineno = aComment.getStartLineNo();
298         final Stack JavaDoc htmlStack = new Stack JavaDoc();
299         final String JavaDoc[] text = aComment.getText();
300         final List JavaDoc typeParameters =
301             CheckUtils.getTypeParameterNames(aAST);
302
303         TagParser parser = null;
304         parser = new TagParser(text, lineno);
305
306         while (parser.hasNextTag()) {
307             final HtmlTag tag = parser.nextTag();
308
309             if (tag.isIncompleteTag()) {
310                 log(tag.getLineno(), "javadoc.incompleteTag",
311                     new Object JavaDoc[] {text[tag.getLineno() - lineno]});
312                 return;
313             }
314             if (tag.isClosedTag()) {
315                 //do nothing
316
continue;
317             }
318             if (!tag.isCloseTag()) {
319                 htmlStack.push(tag);
320             }
321             else {
322                 // We have found a close tag.
323
if (isExtraHtml(tag.getId(), htmlStack)) {
324                     // No corresponding open tag was found on the stack.
325
log(tag.getLineno(),
326                         tag.getPosition(),
327                         EXTRA_HTML,
328                         tag);
329                 }
330                 else {
331                     // See if there are any unclosed tags that were opened
332
// after this one.
333
checkUnclosedTags(htmlStack, tag.getId());
334                 }
335             }
336         }
337
338         // Identify any tags left on the stack.
339
String JavaDoc lastFound = ""; // Skip multiples, like <b>...<b>
340
for (int i = 0; i < htmlStack.size(); i++) {
341             final HtmlTag htag = (HtmlTag) htmlStack.elementAt(i);
342             if (!isSingleTag(htag)
343                 && !htag.getId().equals(lastFound)
344                 && !typeParameters.contains(htag.getId()))
345             {
346                 log(htag.getLineno(), htag.getPosition(), UNCLOSED_HTML, htag);
347                 lastFound = htag.getId();
348             }
349         }
350     }
351
352     /**
353      * Checks to see if there are any unclosed tags on the stack. The token
354      * represents a html tag that has been closed and has a corresponding open
355      * tag on the stack. Any tags, except single tags, that were opened
356      * (pushed on the stack) after the token are missing a close.
357      *
358      * @param aHtmlStack the stack of opened HTML tags.
359      * @param aToken the current HTML tag name that has been closed.
360      */

361     private void checkUnclosedTags(Stack JavaDoc aHtmlStack, String JavaDoc aToken)
362     {
363         final Stack JavaDoc unclosedTags = new Stack JavaDoc();
364         HtmlTag lastOpenTag = (HtmlTag) aHtmlStack.pop();
365         while (!aToken.equalsIgnoreCase(lastOpenTag.getId())) {
366             // Find unclosed elements. Put them on a stack so the
367
// output order won't be back-to-front.
368
if (isSingleTag(lastOpenTag)) {
369                 lastOpenTag = (HtmlTag) aHtmlStack.pop();
370             }
371             else {
372                 unclosedTags.push(lastOpenTag);
373                 lastOpenTag = (HtmlTag) aHtmlStack.pop();
374             }
375         }
376
377         // Output the unterminated tags, if any
378
String JavaDoc lastFound = ""; // Skip multiples, like <b>..<b>
379
for (int i = 0; i < unclosedTags.size(); i++) {
380             lastOpenTag = (HtmlTag) unclosedTags.get(i);
381             if (lastOpenTag.getId().equals(lastFound)) {
382                 continue;
383             }
384             lastFound = lastOpenTag.getId();
385             log(lastOpenTag.getLineno(),
386                 lastOpenTag.getPosition(),
387                 UNCLOSED_HTML,
388                 lastOpenTag);
389         }
390     }
391
392     /**
393      * Determines if the HtmlTag is one which does not require a close tag.
394      *
395      * @param aTag the HtmlTag to check.
396      * @return <code>true</code> if the HtmlTag is a single tag.
397      */

398     private boolean isSingleTag(HtmlTag aTag)
399     {
400         boolean isSingleTag = false;
401         for (int i = 0; i < SINGLE_TAG.length; i++) {
402             // If its a singleton tag (<p>, <br>, etc.), ignore it
403
// Can't simply not put them on the stack, since singletons
404
// like <dt> and <dd> (unhappily) may either be terminated
405
// or not terminated. Both options are legal.
406
if (aTag.getId().equalsIgnoreCase(SINGLE_TAG[i])) {
407                 isSingleTag = true;
408             }
409         }
410         return isSingleTag;
411     }
412
413     /**
414      * Determines if the given token is an extra HTML tag. This indicates that
415      * a close tag was found that does not have a corresponding open tag.
416      *
417      * @param aToken an HTML tag id for which a close was found.
418      * @param aHtmlStack a Stack of previous open HTML tags.
419      * @return <code>false</code> if a previous open tag was found
420      * for the token.
421      */

422     private boolean isExtraHtml(String JavaDoc aToken, Stack JavaDoc aHtmlStack)
423     {
424         boolean isExtra = true;
425         for (int i = 0; i < aHtmlStack.size(); i++) {
426             // Loop, looking for tags that are closed.
427
// The loop is needed in case there are unclosed
428
// tags on the stack. In that case, the stack would
429
// not be empty, but this tag would still be extra.
430
final HtmlTag td = (HtmlTag) aHtmlStack.elementAt(i);
431             if (aToken.equalsIgnoreCase(td.getId())) {
432                 isExtra = false;
433                 break;
434             }
435         }
436
437         return isExtra;
438     }
439
440     /**
441      * Sets the scope to check.
442      * @param aFrom string to get the scope from
443      */

444     public void setScope(String JavaDoc aFrom)
445     {
446         mScope = Scope.getInstance(aFrom);
447     }
448
449     /**
450      * Set the excludeScope.
451      * @param aScope a <code>String</code> value
452      */

453     public void setExcludeScope(String JavaDoc aScope)
454     {
455         mExcludeScope = Scope.getInstance(aScope);
456     }
457
458     /**
459      * Returns a regular expression for matching the end of a sentence.
460      *
461      * @return a regular expression for matching the end of a sentence.
462      */

463     private Pattern JavaDoc getEndOfSentencePattern()
464     {
465         if (mEndOfSentencePattern == null) {
466             try {
467                 mEndOfSentencePattern =
468                     Pattern.compile("([.?!][ \t\n\r\f<])|([.?!]$)");
469             }
470             catch (final PatternSyntaxException JavaDoc e) {
471                 // This should never occur.
472
e.printStackTrace();
473             }
474         }
475         return mEndOfSentencePattern;
476     }
477
478     /**
479      * Sets the flag that determines if the first sentence is checked for
480      * proper end of sentence punctuation.
481      * @param aFlag <code>true</code> if the first sentence is to be checked
482      */

483     public void setCheckFirstSentence(boolean aFlag)
484     {
485         mCheckFirstSentence = aFlag;
486     }
487
488     /**
489      * Sets the flag that determines if HTML checking is to be performed.
490      * @param aFlag <code>true</code> if HTML checking is to be performed.
491      */

492     public void setCheckHtml(boolean aFlag)
493     {
494         mCheckHtml = aFlag;
495     }
496
497     /**
498      * Sets the flag that determines if empty JavaDoc checking should be done.
499      * @param aFlag <code>true</code> if empty JavaDoc checking should be done.
500      */

501     public void setCheckEmptyJavadoc(boolean aFlag)
502     {
503         mCheckEmptyJavadoc = aFlag;
504     }
505 }
506
Popular Tags