Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
TagParser |
|
| 3.0714285714285716;3.071 | ||||
TagParser$Point |
|
| 3.0714285714285716;3.071 |
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.checks.javadoc; | |
20 | ||
21 | import com.google.common.collect.Lists; | |
22 | import java.util.List; | |
23 | ||
24 | /** | |
25 | * <p> | |
26 | * Helper class used to parse HTML tags or generic type identifiers | |
27 | * from a single line of text. Just the beginning of the HTML tag | |
28 | * is located. No attempt is made to parse out the complete tag, | |
29 | * particularly since some of the tag parameters could be located | |
30 | * on the following line of text. The <code>hasNextTag</code> and | |
31 | * <code>nextTag</code> methods are used to iterate through the HTML | |
32 | * tags or generic type identifiers that were found on the line of text. | |
33 | * </p> | |
34 | * | |
35 | * <p> | |
36 | * This class isn't really specific to HTML tags. Currently the only HTML | |
37 | * tag that this class looks specifically for is the HTML comment tag. | |
38 | * This class helps figure out if a tag exists and if it is well-formed. | |
39 | * It does not know whether it is valid HTML. This class is also used for | |
40 | * generics types which looks like opening HTML tags ex: <T>, <E>, <V>, | |
41 | * <MY_FOO_TYPE>, etc. According to this class they are valid tags. | |
42 | * </p> | |
43 | * @author Chris Stillwell | |
44 | */ | |
45 | class TagParser | |
46 | { | |
47 | /** List of HtmlTags found on the input line of text. */ | |
48 | 154 | private final List<HtmlTag> mTags = Lists.newLinkedList(); |
49 | ||
50 | /** | |
51 | * Constructs a TagParser and finds the first tag if any. | |
52 | * @param aText the line of text to parse. | |
53 | * @param aLineNo the source line number. | |
54 | */ | |
55 | public TagParser(String[] aText, int aLineNo) | |
56 | 154 | { |
57 | 154 | parseTags(aText, aLineNo); |
58 | 154 | } |
59 | ||
60 | /** | |
61 | * Returns the next available HtmlTag. | |
62 | * @return a HtmlTag or <code>null</code> if none available. | |
63 | * @throws IndexOutOfBoundsException if there are no HtmlTags | |
64 | * left to return. | |
65 | */ | |
66 | public HtmlTag nextTag() | |
67 | { | |
68 | 166 | return mTags.remove(0); |
69 | } | |
70 | ||
71 | /** | |
72 | * Indicates if there are any more HtmlTag to retrieve. | |
73 | * @return <code>true</code> if there are more tags. | |
74 | */ | |
75 | public boolean hasNextTag() | |
76 | { | |
77 | 315 | return !mTags.isEmpty(); |
78 | } | |
79 | ||
80 | /** | |
81 | * Performs lazy initialization on the internal tags List | |
82 | * and adds the tag. | |
83 | * @param aTag the HtmlTag to add. | |
84 | */ | |
85 | private void add(HtmlTag aTag) | |
86 | { | |
87 | 166 | mTags.add(aTag); |
88 | 166 | } |
89 | ||
90 | /** | |
91 | * Parses the text line for any HTML tags and adds them to the internal | |
92 | * List of tags. | |
93 | * @param aText the source line to parse. | |
94 | * @param aLineNo the source line number. | |
95 | */ | |
96 | private void parseTags(String[] aText, int aLineNo) | |
97 | { | |
98 | 154 | final int nLines = aText.length; |
99 | 154 | Point position = new Point(0, 0); |
100 | ||
101 | 154 | position = findChar(aText, '<', position); |
102 | 331 | while (position.getLineNo() < nLines) { |
103 | // if this is html comment then skip it | |
104 | 177 | if (isCommentTag(aText, position)) { |
105 | 3 | position = skipHtmlComment(aText, position); |
106 | } | |
107 | 174 | else if (!isTag(aText, position)) { |
108 | 8 | position = getNextCharPos(aText, position); |
109 | } | |
110 | else { | |
111 | // find end of tag | |
112 | 166 | final Point endTag = findChar(aText, '>', position); |
113 | 166 | final boolean incompleteTag = (endTag.getLineNo() >= nLines); |
114 | // get tag id (one word) | |
115 | 166 | final String tagId = |
116 | (incompleteTag ? "" : getTagId(aText, position)); | |
117 | // is this closed tag | |
118 | 166 | final boolean closedTag = |
119 | ((endTag.getLineNo() < nLines) && (endTag.getColumnNo() > 0) | |
120 | && (aText[endTag.getLineNo()] | |
121 | .charAt(endTag.getColumnNo() - 1) == '/')); | |
122 | // add new tag | |
123 | 166 | add(new HtmlTag(tagId, |
124 | position.getLineNo() + aLineNo, | |
125 | position.getColumnNo(), | |
126 | closedTag, | |
127 | incompleteTag, | |
128 | aText[position.getLineNo()])); | |
129 | 166 | position = endTag; |
130 | } | |
131 | 177 | position = findChar(aText, '<', position); |
132 | } | |
133 | 154 | } |
134 | ||
135 | /** | |
136 | * Checks if the given position is start one for HTML tag. | |
137 | * @param aText text of javadoc comments. | |
138 | * @param aPos position to check. | |
139 | * @return <code>true</code> some HTML tag starts from given position. | |
140 | */ | |
141 | private boolean isTag(String[] aText, Point aPos) | |
142 | { | |
143 | 174 | final int column = aPos.getColumnNo() + 1; |
144 | 174 | final String text = aText[aPos.getLineNo()]; |
145 | ||
146 | //Character.isJavaIdentifier... may not be a valid HTML | |
147 | //identifier but is valid for generics | |
148 | 174 | return ((column < text.length()) |
149 | && (Character.isJavaIdentifierStart(text.charAt(column)) | |
150 | || Character.isJavaIdentifierPart(text.charAt(column)) | |
151 | || text.charAt(column) == '/') | |
152 | || (column >= text.length())); | |
153 | } | |
154 | ||
155 | /** | |
156 | * Parse tag id. | |
157 | * @param aText text of javadoc comments. | |
158 | * @param aTagStart start position of the tag | |
159 | * @return id for given tag | |
160 | */ | |
161 | private String getTagId(String[] aText, Point aTagStart) | |
162 | { | |
163 | 161 | int column = aTagStart.getColumnNo() + 1; |
164 | 161 | String text = aText[aTagStart.getLineNo()]; |
165 | 161 | if (column >= text.length()) { |
166 | 0 | return ""; |
167 | } | |
168 | ||
169 | 161 | if (text.charAt(column) == '/') { |
170 | 43 | column++; |
171 | } | |
172 | ||
173 | 161 | text = text.substring(column).trim(); |
174 | 161 | column = 0; |
175 | ||
176 | //Character.isJavaIdentifier... may not be a valid HTML | |
177 | //identifier but is valid for generics | |
178 | while (column < text.length() | |
179 | 723 | && (Character.isJavaIdentifierStart(text.charAt(column)) |
180 | || Character.isJavaIdentifierPart(text.charAt(column)))) | |
181 | { | |
182 | 562 | column++; |
183 | } | |
184 | ||
185 | 161 | return text.substring(0, column); |
186 | } | |
187 | ||
188 | /** | |
189 | * If this is a HTML-comments. | |
190 | * @param aText text of javadoc comments | |
191 | * @param aPos position to check | |
192 | * @return <code>true</code> if HTML-comments | |
193 | * starts form given position. | |
194 | */ | |
195 | private boolean isCommentTag(String[] aText, Point aPos) | |
196 | { | |
197 | 177 | return aText[aPos.getLineNo()].startsWith("<!--", aPos.getColumnNo()); |
198 | } | |
199 | ||
200 | /** | |
201 | * Skips HTML comments. | |
202 | * @param aText text of javadoc comments. | |
203 | * @param aFrom start position of HTML-comments | |
204 | * @return position after HTML-comments | |
205 | */ | |
206 | private Point skipHtmlComment(String[] aText, Point aFrom) | |
207 | { | |
208 | 3 | Point to = aFrom; |
209 | 3 | to = findChar(aText, '>', to); |
210 | while ((to.getLineNo() < aText.length) | |
211 | 9 | && !aText[to.getLineNo()] |
212 | .substring(0, to.getColumnNo()).endsWith("-->")) | |
213 | { | |
214 | 6 | to = findChar(aText, '>', getNextCharPos(aText, to)); |
215 | } | |
216 | 3 | return to; |
217 | } | |
218 | ||
219 | /** | |
220 | * Finds next occurrence of given character. | |
221 | * @param aText text to search | |
222 | * @param aChar character to search | |
223 | * @param aFrom position to start search | |
224 | * @return position of next occurrence of given character | |
225 | */ | |
226 | private Point findChar(String[] aText, char aChar, Point aFrom) | |
227 | { | |
228 | 506 | Point curr = new Point(aFrom.getLineNo(), aFrom.getColumnNo()); |
229 | while ((curr.getLineNo() < aText.length) | |
230 | 11936 | && (aText[curr.getLineNo()].charAt(curr.getColumnNo()) != aChar)) |
231 | { | |
232 | 11430 | curr = getNextCharPos(aText, curr); |
233 | } | |
234 | ||
235 | 506 | return curr; |
236 | } | |
237 | ||
238 | /** | |
239 | * Returns position of next comment character, skips | |
240 | * whitespaces and asterisks. | |
241 | * @param aText to search. | |
242 | * @param aFrom location to search from | |
243 | * @return location of the next character. | |
244 | */ | |
245 | private Point getNextCharPos(String[] aText, Point aFrom) | |
246 | { | |
247 | 11444 | int line = aFrom.getLineNo(); |
248 | 11444 | int column = aFrom.getColumnNo() + 1; |
249 | 12214 | while ((line < aText.length) && (column >= aText[line].length())) { |
250 | // go to the next line | |
251 | 770 | line++; |
252 | 770 | column = 0; |
253 | 770 | if (line < aText.length) { |
254 | //skip beginning spaces and stars | |
255 | 616 | final String currentLine = aText[line]; |
256 | while ((column < currentLine.length()) | |
257 | 6134 | && (Character.isWhitespace(currentLine.charAt(column)) |
258 | || (currentLine.charAt(column) == '*'))) | |
259 | { | |
260 | 5518 | column++; |
261 | 5518 | if ((column < currentLine.length()) |
262 | && (currentLine.charAt(column - 1) == '*') | |
263 | && (currentLine.charAt(column) == '/')) | |
264 | { | |
265 | // this is end of comment | |
266 | 136 | column = currentLine.length(); |
267 | } | |
268 | } | |
269 | 616 | } |
270 | } | |
271 | ||
272 | 11444 | return new Point(line, column); |
273 | } | |
274 | ||
275 | /** | |
276 | * Represents current position in the text. | |
277 | * @author o_sukholsky | |
278 | */ | |
279 | private static final class Point | |
280 | { | |
281 | /** line number. */ | |
282 | private final int mLine; | |
283 | /** column number.*/ | |
284 | private final int mColumn; | |
285 | ||
286 | /** | |
287 | * Creates new <code>Point</code> instance. | |
288 | * @param aLineNo line number | |
289 | * @param aColumnNo column number | |
290 | */ | |
291 | public Point(int aLineNo, int aColumnNo) | |
292 | 12104 | { |
293 | 12104 | mLine = aLineNo; |
294 | 12104 | mColumn = aColumnNo; |
295 | 12104 | } |
296 | ||
297 | /** | |
298 | * Getter for line number. | |
299 | * @return line number of the position. | |
300 | */ | |
301 | public int getLineNo() | |
302 | { | |
303 | 37343 | return mLine; |
304 | } | |
305 | ||
306 | /** | |
307 | * Getter for column number. | |
308 | * @return column number of the position. | |
309 | */ | |
310 | public int getColumnNo() | |
311 | { | |
312 | 24730 | return mColumn; |
313 | } | |
314 | } | |
315 | } |