View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  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.api;
20  
21  import com.google.common.collect.ImmutableMap;
22  
23  import com.google.common.collect.Lists;
24  import com.google.common.collect.Maps;
25  import com.puppycrawl.tools.checkstyle.grammars.CommentListener;
26  import java.io.File;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.regex.Pattern;
32  
33  /**
34   * Represents the contents of a file.
35   *
36   * @author Oliver Burn
37   * @version 1.0
38   */
39  public final class FileContents implements CommentListener
40  {
41      /**
42       * the pattern to match a single line comment containing only the comment
43       * itself -- no code.
44       */
45      private static final String MATCH_SINGLELINE_COMMENT_PAT = "^\\s*//.*$";
46      /** compiled regexp to match a single-line comment line */
47      private static final Pattern MATCH_SINGLELINE_COMMENT = Pattern
48              .compile(MATCH_SINGLELINE_COMMENT_PAT);
49  
50      /** the file name */
51      private final String filename;
52  
53      /** the text */
54      private final FileText text;
55  
56      /** map of the Javadoc comments indexed on the last line of the comment.
57       * The hack is it assumes that there is only one Javadoc comment per line.
58       */
59      private final Map<Integer, TextBlock> javadocComments = Maps.newHashMap();
60      /** map of the C++ comments indexed on the first line of the comment. */
61      private final Map<Integer, TextBlock> cppComments =
62          Maps.newHashMap();
63  
64      /**
65       * map of the C comments indexed on the first line of the comment to a list
66       * of comments on that line
67       */
68      private final Map<Integer, List<TextBlock>> clangComments = Maps.newHashMap();
69  
70      /**
71       * Creates a new <code>FileContents</code> instance.
72       *
73       * @param filename name of the file
74       * @param lines the contents of the file
75       * @deprecated Use {@link #FileContents(FileText)} instead
76       *   in order to preserve the original line breaks where possible.
77       */
78      @Deprecated public FileContents(String filename, String[] lines)
79      {
80          this.filename = filename;
81          text = FileText.fromLines(new File(filename), Arrays.asList(lines));
82      }
83  
84      /**
85       * Creates a new <code>FileContents</code> instance.
86       *
87       * @param text the contents of the file
88       */
89      public FileContents(FileText text)
90      {
91          filename = text.getFile().toString();
92          this.text = text;
93      }
94  
95      /** {@inheritDoc} */
96      @Override
97      public void reportSingleLineComment(String type, int startLineNo,
98              int startColNo)
99      {
100         reportCppComment(startLineNo, startColNo);
101     }
102 
103     /** {@inheritDoc} */
104     @Override
105     public void reportBlockComment(String type, int startLineNo,
106             int startColNo, int endLineNo, int endColNo)
107     {
108         reportCComment(startLineNo, startColNo, endLineNo, endColNo);
109     }
110 
111     /**
112      * Report the location of a C++ style comment.
113      * @param startLineNo the starting line number
114      * @param startColNo the starting column number
115      **/
116     public void reportCppComment(int startLineNo, int startColNo)
117     {
118         final String line = line(startLineNo - 1);
119         final String[] txt = new String[] {line.substring(startColNo)};
120         final Comment comment = new Comment(txt, startColNo, startLineNo,
121                 line.length() - 1);
122         cppComments.put(startLineNo, comment);
123     }
124 
125     /**
126      * Returns a map of all the C++ style comments. The key is a line number,
127      * the value is the comment {@link TextBlock} at the line.
128      * @return the Map of comments
129      */
130     public ImmutableMap<Integer, TextBlock> getCppComments()
131     {
132         return ImmutableMap.copyOf(cppComments);
133     }
134 
135     /**
136      * Report the location of a C-style comment.
137      * @param startLineNo the starting line number
138      * @param startColNo the starting column number
139      * @param endLineNo the ending line number
140      * @param endColNo the ending column number
141      **/
142     public void reportCComment(int startLineNo, int startColNo,
143             int endLineNo, int endColNo)
144     {
145         final String[] cc = extractCComment(startLineNo, startColNo,
146                 endLineNo, endColNo);
147         final Comment comment = new Comment(cc, startColNo, endLineNo,
148                 endColNo);
149 
150         // save the comment
151         if (clangComments.containsKey(startLineNo)) {
152             final List<TextBlock> entries = clangComments.get(startLineNo);
153             entries.add(comment);
154         }
155         else {
156             final List<TextBlock> entries = Lists.newArrayList();
157             entries.add(comment);
158             clangComments.put(startLineNo, entries);
159         }
160 
161         // Remember if possible Javadoc comment
162         if (line(startLineNo - 1).indexOf("/**", startColNo) != -1) {
163             javadocComments.put(endLineNo - 1, comment);
164         }
165     }
166 
167     /**
168      * Returns a map of all C style comments. The key is the line number, the
169      * value is a {@link List} of C style comment {@link TextBlock}s
170      * that start at that line.
171      * @return the map of comments
172      */
173     public ImmutableMap<Integer, List<TextBlock>> getCComments()
174     {
175         return ImmutableMap.copyOf(clangComments);
176     }
177 
178     /**
179      * Returns the specified C comment as a String array.
180      * @return C comment as a array
181      * @param startLineNo the starting line number
182      * @param startColNo the starting column number
183      * @param endLineNo the ending line number
184      * @param endColNo the ending column number
185      **/
186     private String[] extractCComment(int startLineNo, int startColNo,
187             int endLineNo, int endColNo)
188     {
189         String[] retVal;
190         if (startLineNo == endLineNo) {
191             retVal = new String[1];
192             retVal[0] = line(startLineNo - 1).substring(startColNo,
193                     endColNo + 1);
194         }
195         else {
196             retVal = new String[endLineNo - startLineNo + 1];
197             retVal[0] = line(startLineNo - 1).substring(startColNo);
198             for (int i = startLineNo; i < endLineNo; i++) {
199                 retVal[i - startLineNo + 1] = line(i);
200             }
201             retVal[retVal.length - 1] = line(endLineNo - 1).substring(0,
202                     endColNo + 1);
203         }
204         return retVal;
205     }
206 
207     /**
208      * Returns the Javadoc comment before the specified line.
209      * A return value of <code>null</code> means there is no such comment.
210      * @return the Javadoc comment, or <code>null</code> if none
211      * @param lineNoBefore the line number to check before
212      **/
213     public TextBlock getJavadocBefore(int lineNoBefore)
214     {
215         // Lines start at 1 to the callers perspective, so need to take off 2
216         int lineNo = lineNoBefore - 2;
217 
218         // skip blank lines
219         while ((lineNo > 0) && (lineIsBlank(lineNo) || lineIsComment(lineNo))) {
220             lineNo--;
221         }
222 
223         return javadocComments.get(lineNo);
224     }
225 
226     /**
227      * Get a single line.
228      * For internal use only, as getText().get(lineNo) is just as
229      * suitable for external use and avoids method duplication.
230      * @param lineNo the number of the line to get
231      * @return the corresponding line, without terminator
232      * @throws IndexOutOfBoundsException if lineNo is invalid
233      */
234     private String line(int lineNo)
235     {
236         return text.get(lineNo);
237     }
238 
239     /**
240      * Get the full text of the file.
241      * @return an object containing the full text of the file
242      */
243     public FileText getText()
244     {
245         return text;
246     }
247 
248     /** @return the lines in the file */
249     public String[] getLines()
250     {
251         return text.toLinesArray();
252     }
253 
254     /**
255      * Get the line from text of the file.
256      * @param index index of the line
257      * @return line from text of the file
258      */
259     public String getLine(int index)
260     {
261         return text.get(index);
262     }
263 
264     /** @return the name of the file */
265     public String getFilename()
266     {
267         return filename;
268     }
269 
270     /**
271      * Checks if the specified line is blank.
272      * @param lineNo the line number to check
273      * @return if the specified line consists only of tabs and spaces.
274      **/
275     public boolean lineIsBlank(int lineNo)
276     {
277         // possible improvement: avoid garbage creation in trim()
278         return "".equals(line(lineNo).trim());
279     }
280 
281     /**
282      * Checks if the specified line is a single-line comment without code.
283      * @param lineNo  the line number to check
284      * @return if the specified line consists of only a single line comment
285      *         without code.
286      **/
287     public boolean lineIsComment(int lineNo)
288     {
289         return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches();
290     }
291 
292     /**
293      * Checks if the specified position intersects with a comment.
294      * @param startLineNo the starting line number
295      * @param startColNo the starting column number
296      * @param endLineNo the ending line number
297      * @param endColNo the ending column number
298      * @return true if the positions intersects with a comment.
299      **/
300     public boolean hasIntersectionWithComment(int startLineNo,
301             int startColNo, int endLineNo, int endColNo)
302     {
303         // Check C comments (all comments should be checked)
304         final Collection<List<TextBlock>> values = clangComments.values();
305         for (final List<TextBlock> row : values) {
306             for (final TextBlock comment : row) {
307                 if (comment.intersects(startLineNo, startColNo, endLineNo,
308                         endColNo))
309                 {
310                     return true;
311                 }
312             }
313         }
314 
315         // Check CPP comments (line searching is possible)
316         for (int lineNumber = startLineNo; lineNumber <= endLineNo;
317              lineNumber++)
318         {
319             final TextBlock comment = cppComments.get(lineNumber);
320             if ((comment != null)
321                     && comment.intersects(startLineNo, startColNo,
322                             endLineNo, endColNo))
323             {
324                 return true;
325             }
326         }
327         return false;
328     }
329 
330     /**
331      * Checks if the current file is a package-info.java file.
332      * @return true if the package file.
333      */
334     public boolean inPackageInfo()
335     {
336         return this.getFilename().endsWith("package-info.java");
337     }
338 }