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.FileReader;
24 import java.io.IOException;
25 import java.io.LineNumberReader;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32
33 import org.apache.maven.plugin.logging.Log;
34 import org.apache.maven.reporting.MavenReportException;
35 import org.codehaus.mojo.taglist.beans.FileReport;
36 import org.codehaus.mojo.taglist.beans.TagReport;
37 import org.codehaus.plexus.util.FileUtils;
38 import org.codehaus.plexus.util.IOUtil;
39
40 /**
41 * Class that analyses a file with a special comment tag. For instance:
42 *
43 * <pre>
44 * // TODO: Example of an Eclipse/IntelliJ-like "todo" tag
45 * </pre>
46 *
47 * @author <a href="mailto:bellingard.NO-SPAM@gmail.com">Fabrice Bellingard </a>
48 * @todo : This is another example of "todo" tag
49 */
50 public class FileAnalyser
51 {
52 /**
53 * String that is used for beginning a comment line.
54 */
55 private static final String STAR_COMMENT = "*";
56
57 /**
58 * String that is used for beginning a comment line.
59 */
60 private static final String SLASH_COMMENT = "//";
61
62 /**
63 * The directories to analyse.
64 */
65 private Collection sourceDirs;
66
67 /**
68 * Log for debug output.
69 */
70 private Log log;
71
72 /**
73 * Map containing tag names as keys ("TODO" or "@todo" for instance), and a TagReport object as value.
74 */
75 private Map tagReportsMap;
76
77 /**
78 * Set to true if the analyser should look for multiple line comments.
79 */
80 private boolean multipleLineCommentsOn;
81
82 /**
83 * Set to true if the analyser should look for tags without comments.
84 */
85 private boolean emptyCommentsOn;
86
87 /**
88 * String used to indicate that there is no comment after the tag.
89 */
90 private String noCommentString;
91
92 /**
93 * Constructor.
94 *
95 * @param report the MOJO that is using this analyser.
96 */
97 public FileAnalyser( TagListReport report )
98 {
99 multipleLineCommentsOn = report.isMultipleLineComments();
100 emptyCommentsOn = report.isEmptyComments();
101 log = report.getLog();
102 sourceDirs = report.constructSourceDirs();
103 noCommentString = report.getBundle().getString( "report.taglist.nocomment" );
104 // init the map of tag reports
105 String[] tags = report.getTags();
106 tagReportsMap = new HashMap( tags.length );
107 for ( int i = 0; i < tags.length; i++ )
108 {
109 String tagName = tags[i];
110 TagReport tagReport = new TagReport( tagName );
111 tagReportsMap.put( tagName, tagReport );
112 }
113 }
114
115 /**
116 * Execute the analysis for the configuration given by the TagListReport.
117 *
118 * @return a collection of TagReport objects.
119 * @throws MavenReportException the Maven report exception.
120 */
121 public Collection execute()
122 throws MavenReportException
123 {
124 List fileList = findFilesToScan();
125
126 for ( Iterator iter = fileList.iterator(); iter.hasNext(); )
127 {
128 File file = (File) iter.next();
129 if ( file.exists() )
130 {
131 scanFile( file );
132 }
133 }
134
135 return tagReportsMap.values();
136 }
137
138 /**
139 * Gives the list of files to scan.
140 *
141 * @return a List of File objects.
142 * @throws MavenReportException the Maven report exception.
143 */
144 private List findFilesToScan()
145 throws MavenReportException
146 {
147 List filesList = new ArrayList();
148 try
149 {
150 for ( Iterator iter = sourceDirs.iterator(); iter.hasNext(); )
151 {
152 filesList.addAll( FileUtils.getFiles( new File( (String) iter.next() ), "**/*.java", null ) );
153 }
154 }
155 catch ( IOException e )
156 {
157 throw new MavenReportException( "Error while trying to find the files to scan.", e );
158 }
159 return filesList;
160 }
161
162 /**
163 * Scans a file to look for task tags.
164 *
165 * @param file the file to scan.
166 */
167 public void scanFile( File file )
168 {
169 LineNumberReader reader = null;
170
171 try
172 {
173 reader = new LineNumberReader( new FileReader( file ) );
174
175 String currentLine = reader.readLine();
176 while ( currentLine != null )
177 {
178 int index = -1;
179 String tagName = null;
180
181 int[] indices = new int[tagReportsMap.keySet().size()];
182 String[] tagNames = new String[tagReportsMap.keySet().size()];
183 int counter = 0;
184 boolean found = false;
185 // look for a tag on this line
186 for ( Iterator iter = tagReportsMap.keySet().iterator(); iter.hasNext(); )
187 {
188 tagName = (String) iter.next();
189 index = currentLine.indexOf( tagName );
190 tagNames[counter] = tagName;
191 indices[counter] = index;
192 if ( index >= 0 )
193 {
194 found = true;
195 }
196
197 counter++;
198 }
199
200 if ( !found || tagName == null )
201 {
202 // no tag on this line: just go on next line
203 currentLine = reader.readLine();
204 continue;
205 }
206
207 // there's a tag on this line
208 String commentType = null;
209 for ( int i = 0; i < indices.length; i++ )
210 {
211 if ( indices[i] >= 0 )
212 {
213 commentType = extractCommentType( currentLine, indices[i] );
214 }
215 if ( commentType != null )
216 {
217 index = indices[i];
218 tagName = tagNames[i];
219 break;
220 }
221 }
222
223 if ( commentType == null )
224 {
225 // this is not a valid comment tag: go to the next line
226 currentLine = reader.readLine();
227 continue;
228 }
229
230 int tagLength = tagName.length();
231 int commentStartIndex = reader.getLineNumber();
232 StringBuffer comment = new StringBuffer();
233
234 String firstLine = currentLine.substring( index + tagLength ).trim();
235 if ( firstLine.length() == 0 )
236 {
237 // this is not a valid comment tag: nothing is written there
238 currentLine = reader.readLine();
239 if ( emptyCommentsOn )
240 {
241 comment.append( "--" );
242 comment.append( noCommentString );
243 comment.append( "--" );
244 }
245 else
246 {
247 continue;
248 }
249 }
250 else
251 {
252 // this tag has a comment
253 if ( firstLine.charAt( 0 ) == ':' )
254 {
255 comment.append( firstLine.substring( 1 ).trim() );
256 }
257 else
258 {
259 comment.append( firstLine );
260 }
261
262 // next line
263 currentLine = reader.readLine();
264
265 if ( multipleLineCommentsOn )
266 {
267 // we're looking for multiple line comments
268 while ( currentLine != null && currentLine.trim().startsWith( commentType )
269 && currentLine.indexOf( tagName ) < 0 )
270 {
271 String currentComment = currentLine.substring( currentLine.indexOf( commentType )
272 + commentType.length() ).trim();
273 if ( currentComment.startsWith( "@" ) || "".equals( currentComment )
274 || "/".equals( currentComment ) )
275 {
276 // the comment is finished
277 break;
278 }
279 // try to look if the next line is not a new tag
280 boolean newTagFound = false;
281 for ( Iterator iter = tagReportsMap.keySet().iterator(); iter.hasNext(); )
282 {
283 String currentTagName = (String) iter.next();
284 if ( currentComment.startsWith( currentTagName ) )
285 {
286 newTagFound = true;
287 }
288 }
289 if ( newTagFound )
290 {
291 // this is a new comment: stop here the current comment
292 break;
293 }
294 // nothing was found: this means the comment is going on this line
295 comment.append( " " );
296 comment.append( currentComment );
297 currentLine = reader.readLine();
298 }
299 }
300 }
301
302 TagReport tagReport = (TagReport) tagReportsMap.get( tagName );
303 FileReport fileReport = tagReport.getFileReport( file );
304 fileReport.addComment( comment.toString(), commentStartIndex );
305 }
306 }
307 catch ( IOException e )
308 {
309 log.error( "Error while scanning the file " + file.getPath(), e );
310 }
311 finally
312 {
313 IOUtil.close( reader );
314 }
315 }
316
317 /**
318 * Finds the type of comment the tag is in.
319 *
320 * @param currentLine the line to analyse.
321 * @param index the index of the tag in the line.
322 * @return "*" or "//" or null.
323 */
324 private String extractCommentType( String currentLine, int index )
325 {
326 String commentType = null;
327 String beforeTag = currentLine.substring( 0, index ).trim();
328 if ( beforeTag.endsWith( SLASH_COMMENT ) )
329 {
330 commentType = SLASH_COMMENT;
331 }
332 else if ( beforeTag.endsWith( STAR_COMMENT ) )
333 {
334 commentType = STAR_COMMENT;
335 }
336 return commentType;
337 }
338
339 }