KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > puppycrawl > tools > checkstyle > filters > SuppressionCommentFilter


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.filters;
20
21 import java.lang.ref.WeakReference JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27
28 import java.util.regex.Matcher JavaDoc;
29 import java.util.regex.Pattern JavaDoc;
30 import java.util.regex.PatternSyntaxException JavaDoc;
31
32 import org.apache.commons.beanutils.ConversionException;
33
34 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
35 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
36 import com.puppycrawl.tools.checkstyle.api.FileContents;
37 import com.puppycrawl.tools.checkstyle.api.Filter;
38 import com.puppycrawl.tools.checkstyle.api.TextBlock;
39 import com.puppycrawl.tools.checkstyle.api.Utils;
40 import com.puppycrawl.tools.checkstyle.checks.FileContentsHolder;
41
42
43 /**
44  * <p>
45  * A filter that uses comments to suppress audit events.
46  * </p>
47  * <p>
48  * Rationale:
49  * Sometimes there are legitimate reasons for violating a check. When
50  * this is a matter of the code in question and not personal
51  * preference, the best place to override the policy is in the code
52  * itself. Semi-structured comments can be associated with the check.
53  * This is sometimes superior to a separate suppressions file, which
54  * must be kept up-to-date as the source file is edited.
55  * </p>
56  * <p>
57  * Usage:
58  * This check only works in conjunction with the FileContentsHolder module
59  * since that module makes the suppression comments in the .java
60  * files available <i>sub rosa</i>.
61  * </p>
62  * @see FileContentsHolder
63  * @author Mike McMahon
64  * @author Rick Giles
65  */

66 public class SuppressionCommentFilter
67     extends AutomaticBean
68     implements Filter
69 {
70     /**
71      * A Tag holds a suppression comment and its location, and determines
72      * whether the supression turns checkstyle reporting on or off.
73      * @author Rick Giles
74      */

75     public class Tag
76         implements Comparable JavaDoc
77     {
78         /** The text of the tag. */
79         private String JavaDoc mText;
80
81         /** The line number of the tag. */
82         private int mLine;
83
84         /** The column number of the tag. */
85         private int mColumn;
86
87         /** Determines whether the suppression turns checkstyle reporting on. */
88         private boolean mOn;
89
90         /** The parsed check regexp, expanded for the text of this tag. */
91         private Pattern JavaDoc mTagCheckRegexp;
92
93         /** The parsed message regexp, expanded for the text of this tag. */
94         private Pattern JavaDoc mTagMessageRegexp;
95
96         /**
97          * Constructs a tag.
98          * @param aLine the line number.
99          * @param aColumn the column number.
100          * @param aText the text of the suppression.
101          * @param aOn <code>true</code> if the tag turns checkstyle reporting.
102          * @throws ConversionException if unable to parse expanded aText.
103          * on.
104          */

105         public Tag(int aLine, int aColumn, String JavaDoc aText, boolean aOn)
106             throws ConversionException
107         {
108             mLine = aLine;
109             mColumn = aColumn;
110             mText = aText;
111             mOn = aOn;
112
113             mTagCheckRegexp = mCheckRegexp;
114             //Expand regexp for check and message
115
//Does not intern Patterns with Utils.getPattern()
116
String JavaDoc format = "";
117             try {
118                 if (aOn) {
119                     format =
120                         expandFromComment(aText, mCheckFormat, mOnRegexp);
121                     mTagCheckRegexp = Pattern.compile(format);
122                     if (mMessageFormat != null) {
123                         format =
124                             expandFromComment(aText, mMessageFormat, mOnRegexp);
125                         mTagMessageRegexp = Pattern.compile(format);
126                     }
127                 }
128                 else {
129                     format =
130                         expandFromComment(aText, mCheckFormat, mOffRegexp);
131                     mTagCheckRegexp = Pattern.compile(format);
132                     if (mMessageFormat != null) {
133                         format =
134                             expandFromComment(
135                                 aText,
136                                 mMessageFormat,
137                                 mOffRegexp);
138                         mTagMessageRegexp = Pattern.compile(format);
139                     }
140                 }
141             }
142             catch (final PatternSyntaxException JavaDoc e) {
143                 throw new ConversionException(
144                     "unable to parse expanded comment " + format,
145                     e);
146             }
147         }
148
149         /** @return the text of the tag. */
150         public String JavaDoc getText()
151         {
152             return mText;
153         }
154
155         /** @return the line number of the tag in the source file. */
156         public int getLine()
157         {
158             return mLine;
159         }
160
161         /**
162          * Determines the column number of the tag in the source file.
163          * Will be 0 for all lines of multiline comment, except the
164          * first line.
165          * @return the column number of the tag in the source file.
166          */

167         public int getColumn()
168         {
169             return mColumn;
170         }
171
172         /**
173          * Determines whether the suppression turns checkstyle reporting on or
174          * off.
175          * @return <code>true</code>if the suppression turns reporting on.
176          */

177         public boolean isOn()
178         {
179             return mOn;
180         }
181
182         /**
183          * Compares the position of this tag in the file
184          * with the position of another tag.
185          * @param aObject the tag to compare with this one.
186          * @return a negative number if this tag is before the other tag,
187          * 0 if they are at the same position, and a positive number if this
188          * tag is after the other tag.
189          * @see java.lang.Comparable#compareTo(java.lang.Object)
190          */

191         public int compareTo(Object JavaDoc aObject)
192         {
193             final Tag other = (Tag) aObject;
194             if (mLine == other.mLine) {
195                 return mColumn - other.mColumn;
196             }
197
198             return (mLine - other.mLine);
199         }
200
201         /**
202          * Determines whether the source of an audit event
203          * matches the text of this tag.
204          * @param aEvent the <code>AuditEvent</code> to check.
205          * @return true if the source of aEvent matches the text of this tag.
206          */

207         public boolean isMatch(AuditEvent aEvent)
208         {
209             final Matcher JavaDoc tagMatcher =
210                 mTagCheckRegexp.matcher(aEvent.getSourceName());
211             if (tagMatcher.find()) {
212                 return true;
213             }
214             if (mTagMessageRegexp != null) {
215                 final Matcher JavaDoc messageMatcher =
216                     mTagMessageRegexp.matcher(aEvent.getMessage());
217                 return messageMatcher.find();
218             }
219             return false;
220         }
221
222         /**
223          * Expand based on a matching comment.
224          * @param aComment the comment.
225          * @param aString the string to expand.
226          * @param aRegexp the parsed expander.
227          * @return the expanded string
228          */

229         private String JavaDoc expandFromComment(
230             String JavaDoc aComment,
231             String JavaDoc aString,
232             Pattern JavaDoc aRegexp)
233         {
234             final Matcher JavaDoc matcher = aRegexp.matcher(aComment);
235             // Match primarily for effect.
236
if (!matcher.find()) {
237                 ///CLOVER:OFF
238
return aString;
239                 ///CLOVER:ON
240
}
241             String JavaDoc result = aString;
242             for (int i = 0; i <= matcher.groupCount(); i++) {
243                 // $n expands comment match like in Pattern.subst().
244
result = result.replaceAll("\\$" + i, matcher.group(i));
245             }
246             return result;
247         }
248
249         /** {@inheritDoc} */
250         public final String JavaDoc toString()
251         {
252             return "Tag[line=" + getLine() + "; col=" + getColumn()
253                 + "; on=" + isOn() + "; text='" + getText() + "']";
254         }
255     }
256
257     /** Turns checkstyle reporting off. */
258     private static final String JavaDoc DEFAULT_OFF_FORMAT = "CHECKSTYLE\\:OFF";
259
260     /** Turns checkstyle reporting on. */
261     private static final String JavaDoc DEFAULT_ON_FORMAT = "CHECKSTYLE\\:ON";
262
263     /** Control all checks */
264     private static final String JavaDoc DEFAULT_CHECK_FORMAT = ".*";
265
266     /** Whether to look in comments of the C type. */
267     private boolean mCheckC = true;
268
269     /** Whether to look in comments of the C++ type. */
270     private boolean mCheckCPP = true;
271
272     /** Parsed comment regexp that turns checkstyle reporting off. */
273     private Pattern JavaDoc mOffRegexp;
274
275     /** Parsed comment regexp that turns checkstyle reporting on. */
276     private Pattern JavaDoc mOnRegexp;
277
278     /** The check format to suppress. */
279     private String JavaDoc mCheckFormat;
280
281     /** The parsed check regexp. */
282     private Pattern JavaDoc mCheckRegexp;
283
284     /** The message format to suppress. */
285     private String JavaDoc mMessageFormat;
286
287     //TODO: Investigate performance improvement with array
288
/** Tagged comments */
289     private final List JavaDoc mTags = new ArrayList JavaDoc();
290
291     /**
292      * References the current FileContents for this filter.
293      * Since this is a weak reference to the FileContents, the FileContents
294      * can be reclaimed as soon as the strong references in TreeWalker
295      * and FileContentsHolder are reassigned to the next FileContents,
296      * at which time filtering for the current FileContents is finished.
297      */

298     private WeakReference JavaDoc mFileContentsReference = new WeakReference JavaDoc(null);
299
300     /**
301      * Constructs a SuppressionCommentFilter.
302      * Initializes comment on, comment off, and check formats
303      * to defaults.
304      */

305     public SuppressionCommentFilter()
306     {
307         setOnCommentFormat(DEFAULT_ON_FORMAT);
308         setOffCommentFormat(DEFAULT_OFF_FORMAT);
309         setCheckFormat(DEFAULT_CHECK_FORMAT);
310     }
311
312     /**
313      * Set the format for a comment that turns off reporting.
314      * @param aFormat a <code>String</code> value.
315      * @throws ConversionException unable to parse aFormat.
316      */

317     public void setOffCommentFormat(String JavaDoc aFormat)
318         throws ConversionException
319     {
320         try {
321             mOffRegexp = Utils.getPattern(aFormat);
322         }
323         catch (final PatternSyntaxException JavaDoc e) {
324             throw new ConversionException("unable to parse " + aFormat, e);
325         }
326     }
327
328     /**
329      * Set the format for a comment that turns on reporting.
330      * @param aFormat a <code>String</code> value
331      * @throws ConversionException unable to parse aFormat
332      */

333     public void setOnCommentFormat(String JavaDoc aFormat)
334         throws ConversionException
335     {
336         try {
337             mOnRegexp = Utils.getPattern(aFormat);
338         }
339         catch (final PatternSyntaxException JavaDoc e) {
340             throw new ConversionException("unable to parse " + aFormat, e);
341         }
342     }
343
344     /** @return the FileContents for this filter. */
345     public FileContents getFileContents()
346     {
347         return (FileContents) mFileContentsReference.get();
348     }
349
350     /**
351      * Set the FileContents for this filter.
352      * @param aFileContents the FileContents for this filter.
353      */

354     public void setFileContents(FileContents aFileContents)
355     {
356         mFileContentsReference = new WeakReference JavaDoc(aFileContents);
357     }
358
359     /**
360      * Set the format for a check.
361      * @param aFormat a <code>String</code> value
362      * @throws ConversionException unable to parse aFormat
363      */

364     public void setCheckFormat(String JavaDoc aFormat)
365         throws ConversionException
366     {
367         try {
368             mCheckRegexp = Utils.getPattern(aFormat);
369             mCheckFormat = aFormat;
370         }
371         catch (final PatternSyntaxException JavaDoc e) {
372             throw new ConversionException("unable to parse " + aFormat, e);
373         }
374     }
375
376     /**
377      * Set the format for a message.
378      * @param aFormat a <code>String</code> value
379      * @throws ConversionException unable to parse aFormat
380      */

381     public void setMessageFormat(String JavaDoc aFormat)
382         throws ConversionException
383     {
384         // check that aFormat parses
385
try {
386             Utils.getPattern(aFormat);
387         }
388         catch (final PatternSyntaxException JavaDoc e) {
389             throw new ConversionException("unable to parse " + aFormat, e);
390         }
391         mMessageFormat = aFormat;
392     }
393
394
395     /**
396      * Set whether to look in C++ comments.
397      * @param aCheckCPP <code>true</code> if C++ comments are checked.
398      */

399     public void setCheckCPP(boolean aCheckCPP)
400     {
401         mCheckCPP = aCheckCPP;
402     }
403
404     /**
405      * Set whether to look in C comments.
406      * @param aCheckC <code>true</code> if C comments are checked.
407      */

408     public void setCheckC(boolean aCheckC)
409     {
410         mCheckC = aCheckC;
411     }
412
413     /** {@inheritDoc} */
414     public boolean accept(AuditEvent aEvent)
415     {
416         if (aEvent.getLocalizedMessage() == null) {
417             return true; // A special event.
418
}
419
420         // Lazy update. If the first event for the current file, update file
421
// contents and tag suppressions
422
final FileContents currentContents = FileContentsHolder.getContents();
423         if (currentContents == null) {
424             // we have no contents, so we can not filter.
425
// TODO: perhaps we should notify user somehow?
426
return true;
427         }
428         if (getFileContents() != currentContents) {
429             setFileContents(currentContents);
430             tagSuppressions();
431         }
432         final Tag matchTag = findNearestMatch(aEvent);
433         if ((matchTag != null) && !matchTag.isOn()) {
434             return false;
435         }
436         return true;
437     }
438
439     /**
440      * Finds the nearest comment text tag that matches an audit event.
441      * The nearest tag is before the line and column of the event.
442      * @param aEvent the <code>AuditEvent</code> to match.
443      * @return The <code>Tag</code> nearest aEvent.
444      */

445     private Tag findNearestMatch(AuditEvent aEvent)
446     {
447         Tag result = null;
448         // TODO: try binary search if sequential search becomes a performance
449
// problem.
450
for (final Iterator JavaDoc iter = mTags.iterator(); iter.hasNext();) {
451             final Tag tag = (Tag) iter.next();
452             if ((tag.getLine() > aEvent.getLine())
453                 || ((tag.getLine() == aEvent.getLine())
454                     && (tag.getColumn() > aEvent.getColumn())))
455             {
456                 break;
457             }
458             if (tag.isMatch(aEvent)) {
459                 result = tag;
460             }
461         };
462         return result;
463     }
464
465     /**
466      * Collects all the suppression tags for all comments into a list and
467      * sorts the list.
468      */

469     private void tagSuppressions()
470     {
471         mTags.clear();
472         final FileContents contents = getFileContents();
473         if (mCheckCPP) {
474             tagSuppressions(contents.getCppComments().values());
475         }
476         if (mCheckC) {
477             final Collection JavaDoc cComments = contents.getCComments().values();
478             final Iterator JavaDoc iter = cComments.iterator();
479             while (iter.hasNext()) {
480                 final Collection JavaDoc element = (Collection JavaDoc) iter.next();
481                 tagSuppressions(element);
482             }
483         }
484         Collections.sort(mTags);
485     }
486
487     /**
488      * Appends the suppressions in a collection of comments to the full
489      * set of suppression tags.
490      * @param aComments the set of comments.
491      */

492     private void tagSuppressions(Collection JavaDoc aComments)
493     {
494         for (final Iterator JavaDoc iter = aComments.iterator(); iter.hasNext();) {
495             final TextBlock comment = (TextBlock) iter.next();
496             final int startLineNo = comment.getStartLineNo();
497             final String JavaDoc[] text = comment.getText();
498             tagCommentLine(text[0], startLineNo, comment.getStartColNo());
499             for (int i = 1; i < text.length; i++) {
500                 tagCommentLine(text[i], startLineNo + i, 0);
501             }
502         }
503     }
504
505     /**
506      * Tags a string if it matches the format for turning
507      * checkstyle reporting on or the format for turning reporting off.
508      * @param aText the string to tag.
509      * @param aLine the line number of aText.
510      * @param aColumn the column number of aText.
511      */

512     private void tagCommentLine(String JavaDoc aText, int aLine, int aColumn)
513     {
514         final Matcher JavaDoc offMatcher = mOffRegexp.matcher(aText);
515         if (offMatcher.find()) {
516             addTag(offMatcher.group(0), aLine, aColumn, false);
517         }
518         else {
519             final Matcher JavaDoc onMatcher = mOnRegexp.matcher(aText);
520             if (onMatcher.find()) {
521                 addTag(onMatcher.group(0), aLine, aColumn, true);
522             }
523         }
524     }
525
526     /**
527      * Adds a <code>Tag</code> to the list of all tags.
528      * @param aText the text of the tag.
529      * @param aLine the line number of the tag.
530      * @param aColumn the column number of the tag.
531      * @param aOn <code>true</code> if the tag turns checkstyle reporting on.
532      */

533     private void addTag(String JavaDoc aText, int aLine, int aColumn, boolean aOn)
534     {
535         final Tag tag = new Tag(aLine, aColumn, aText, aOn);
536         mTags.add(tag);
537     }
538 }
539
Popular Tags