View Javadoc
1   package org.codehaus.mojo.taglist;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.LineNumberReader;
28  import java.io.Reader;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Locale;
34  
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.maven.plugin.logging.Log;
37  import org.apache.maven.reporting.MavenReportException;
38  import org.codehaus.mojo.taglist.beans.FileReport;
39  import org.codehaus.mojo.taglist.beans.TagReport;
40  import org.codehaus.mojo.taglist.tags.TagClass;
41  import org.codehaus.plexus.util.FileUtils;
42  import org.codehaus.plexus.util.IOUtil;
43  
44  /**
45   * Class that analyzes a file with a special comment tag. For instance:
46   * 
47   * <pre>
48   * // TODO: Example of an Eclipse/IntelliJ-like "todo" tag
49   * </pre>
50   * 
51   * @author <a href="mailto:bellingard.NO-SPAM@gmail.com">Fabrice Bellingard </a>
52   * @todo : This is another example of "todo" tag
53   */
54  public class FileAnalyser
55  {
56      /**
57       * String that is used for beginning a comment line.
58       */
59      private static final String STAR_COMMENT = "*";
60  
61      /**
62       * String that is used for beginning a comment line.
63       */
64      private static final String SLASH_COMMENT = "//";
65      
66      /**
67       * Maximum length of a comment.
68       */
69      private static final int MAX_COMMENT_CHARACTERS = 99999;
70  
71      /**
72       * The character encoding of the files to analyze.
73       */
74      private String encoding;
75      
76      /**
77       * The Locale of the files to analyze.
78       */
79      private Locale locale;
80  
81      /**
82       * The directories to analyze.
83       */
84      private Collection sourceDirs;
85  
86      /**
87       * The files to include, as a comma separated list of patterns.
88       */
89      private String includes;
90  
91      /**
92       * The files top exclude, as a comma separated list of patterns.
93       */
94      private String excludes;
95  
96      /**
97       * Log for debug output.
98       */
99      private Log log;
100 
101     /**
102      * Set to true if the analyzer should look for multiple line comments.
103      */
104     private boolean multipleLineCommentsOn;
105 
106     /**
107      * Set to true if the analyzer should look for tags without comments.
108      */
109     private boolean emptyCommentsOn;
110 
111     /**
112      * String used to indicate that there is no comment after the tag.
113      */
114     private String noCommentString;
115 
116     /**
117      * ArrayList of tag classes.
118      */
119     private List<TagClass> tagClasses = new ArrayList();
120 
121     /**
122      * Constructor.
123      * 
124      * @param report the MOJO that is using this analyzer.
125      * @param tagClasses the array of tag classes to use for searching
126      */
127     public FileAnalyser( TagListReport report, List<TagClass> tagClasses )
128     {
129         multipleLineCommentsOn = report.isMultipleLineComments();
130         emptyCommentsOn = report.isEmptyComments();
131         log = report.getLog();
132         sourceDirs = report.getSourceDirs();
133         encoding = report.getInputEncoding();
134         locale = report.getLocale();
135         noCommentString = report.getBundle().getString( "report.taglist.nocomment" );      
136         this.tagClasses = tagClasses;
137         this.includes = report.getIncludesCommaSeparated();
138         this.excludes = report.getExcludesCommaSeparated();
139     }
140 
141     /**
142      * Execute the analysis for the configuration given by the TagListReport.
143      * 
144      * @return a collection of TagReport objects.
145      * @throws MavenReportException the Maven report exception.
146      */
147     public Collection execute()
148         throws MavenReportException
149     {
150         List fileList = findFilesToScan();
151 
152         for ( Iterator iter = fileList.iterator(); iter.hasNext(); )
153         {
154             File file = (File) iter.next();
155             if ( file.exists() )
156             {
157                 scanFile( file );
158             }
159         }
160 
161         // Get the tag reports from each of the tag classes.
162         Collection tagReports = new ArrayList();
163         Iterator itr = tagClasses.iterator();      
164         while ( itr.hasNext() )
165         {
166             TagClass tc = (TagClass) itr.next();
167             tagReports.add( tc.getTagReport() );
168         }
169 
170         return tagReports;
171     }
172     /**
173      * Gives the list of files to scan.
174      * 
175      * @return a List of File objects.
176      * @throws MavenReportException the Maven report exception.
177      */
178     private List findFilesToScan()
179         throws MavenReportException
180     {
181         List filesList = new ArrayList();
182         try
183         {
184             for ( Iterator iter = sourceDirs.iterator(); iter.hasNext(); )
185             {
186                 filesList.addAll( FileUtils.getFiles( new File( (String) iter.next() ), includes, excludes ) );
187             }
188         }
189         catch ( IOException e )
190         {
191             throw new MavenReportException( "Error while trying to find the files to scan.", e );
192         }
193         return filesList;
194     }
195 
196     /**
197      * Access an input reader that uses the current file encoding.
198      *
199      * @param file the file to open in the reader.
200      * @throws IOException the IO exception.
201      * @return a reader with the current file encoding.
202      */
203     private Reader getReader( File file ) throws IOException
204     {
205         InputStream in = new FileInputStream( file );
206         return ( encoding == null ) ? new InputStreamReader( in ) : new InputStreamReader( in, encoding ); 
207     }
208 
209     /**
210      * Scans a file to look for task tags.
211      * 
212      * @param file the file to scan.
213      */
214     public void scanFile( File file )
215     {
216         LineNumberReader reader = null;
217 
218         try
219         {
220             reader = new LineNumberReader( getReader( file ) );
221 
222             String currentLine = reader.readLine();
223             while ( currentLine != null )
224             {
225                 int index = -1;
226                 Iterator iter = tagClasses.iterator();
227                 // look for a tag on this line
228                 while ( iter.hasNext() )
229                 {
230                     TagClass tagClass = (TagClass) iter.next();
231                     index = tagClass.tagMatchContains( currentLine, locale );
232                     if ( index != TagClass.NO_MATCH )
233                     {
234                         // there's a tag on this line
235                         String commentType = null;
236                         commentType = extractCommentType( currentLine, index );
237 
238                         if ( commentType == null )
239                         {
240                             // this is not a valid comment tag: skip other tag classes and
241                             // go to the next line
242                             break;
243                         }
244 
245                         int tagLength = tagClass.getLastTagMatchStringLength();
246                         int commentStartIndex = reader.getLineNumber();
247                         StringBuffer comment = new StringBuffer();
248 
249                         String firstLine = StringUtils.strip( currentLine.substring( index + tagLength ) );
250                         firstLine = StringUtils.removeEnd( firstLine, "*/" ); //MTAGLIST-35
251                         if ( firstLine.length() == 0 || ":".equals( firstLine ) )
252                         {
253                             // this is not a valid comment tag: nothing is written there
254                             if ( emptyCommentsOn )
255                             {
256                                 comment.append( "--" );
257                                 comment.append( noCommentString );
258                                 comment.append( "--" );
259                             }
260                             else
261                             {                               
262                                 continue;
263                             }
264                         }
265                         else
266                         {
267                             // this tag has a comment
268                             if ( firstLine.charAt( 0 ) == ':' )
269                             {
270                                 comment.append( firstLine.substring( 1 ).trim() );
271                             }
272                             else
273                             {
274                                 comment.append( firstLine );
275                             }
276 
277                             if ( multipleLineCommentsOn )
278                             {
279                                 // Mark the current position, set the read forward limit to
280                                 // a large number that should not be met.
281                                 reader.mark( MAX_COMMENT_CHARACTERS );
282                                 
283                                 // next line
284                                 String futureLine = reader.readLine();
285                                 
286                                 // we're looking for multiple line comments
287                                 while ( futureLine != null && futureLine.trim().startsWith( commentType )
288                                     && futureLine.indexOf( tagClass.getLastTagMatchString() ) < 0 )
289                                 {
290                                     String currentComment = futureLine.substring( futureLine.indexOf( commentType )
291                                                                                    + commentType.length() ).trim();
292                                     if ( currentComment.startsWith( "@" ) || "".equals( currentComment )
293                                         || "/".equals( currentComment ) )
294                                     {
295                                         // the comment is finished
296                                         break;
297                                     }
298                                     // try to look if the next line is not a new tag
299                                     boolean newTagFound = false;
300                                     Iterator moreTCiter = tagClasses.iterator();
301                                     while ( moreTCiter.hasNext() )
302                                     {
303                                         TagClass tc = (TagClass) moreTCiter.next();
304                                         if ( tc.tagMatchStartsWith( currentComment, locale ) )
305                                         {
306                                             newTagFound = true;
307                                             break;
308                                         }
309                                     }
310                                     if ( newTagFound )
311                                     {
312                                         // this is a new comment: stop here the current comment
313                                         break;
314                                     }
315                                     // nothing was found: this means the comment is going on this line
316                                     comment.append( " " );
317                                     comment.append( currentComment );
318                                     futureLine = reader.readLine();
319                                 }
320                                 
321                                 // Reset the reader to the marked position before the multi
322                                 // line check was performed.
323                                 reader.reset();
324                             }
325                         }
326                         TagReport tagReport = tagClass.getTagReport();
327                         FileReport fileReport = tagReport.getFileReport( file, encoding );
328                         fileReport.addComment( comment.toString(), commentStartIndex );
329                     }
330                 }
331                 currentLine = reader.readLine();
332             }
333         }
334         catch ( IOException e )
335         {
336             log.error( "Error while scanning the file " + file.getPath(), e );
337         }
338         finally
339         {
340             IOUtil.close( reader );
341         }
342     }
343 
344     /**
345      * Finds the type of comment the tag is in.
346      * 
347      * @param currentLine the line to analyze.
348      * @param index the index of the tag in the line.
349      * @return "*" or "//" or null.
350      */
351     private String extractCommentType( String currentLine, int index )
352     {
353         String commentType = null;
354         String beforeTag = currentLine.substring( 0, index ).trim();
355         if ( beforeTag.endsWith( SLASH_COMMENT ) )
356         {
357             commentType = SLASH_COMMENT;
358         }
359         else if ( beforeTag.endsWith( STAR_COMMENT ) )
360         {
361             commentType = STAR_COMMENT;
362         }
363         return commentType;
364     }
365 
366 }