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