Fri, 31 Aug 2012 10:37:46 +0100
7151010: Add compiler support for repeating annotations
Reviewed-by: jjg, mcimadamore
1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.javadoc;
28 import com.sun.javadoc.*;
29 import com.sun.tools.javac.util.ListBuffer;
31 /**
32 * Comment contains all information in comment part.
33 * It allows users to get first sentence of this comment, get
34 * comment for different tags...
35 *
36 * @author Kaiyang Liu (original)
37 * @author Robert Field (rewrite)
38 * @author Atul M Dambalkar
39 * @author Neal Gafter (rewrite)
40 */
41 class Comment {
43 /**
44 * sorted comments with different tags.
45 */
46 private final ListBuffer<Tag> tagList = new ListBuffer<Tag>();
48 /**
49 * text minus any tags.
50 */
51 private String text;
53 /**
54 * Doc environment
55 */
56 private final DocEnv docenv;
58 /**
59 * constructor of Comment.
60 */
61 Comment(final DocImpl holder, final String commentString) {
62 this.docenv = holder.env;
64 /**
65 * Separate the comment into the text part and zero to N tags.
66 * Simple state machine is in one of three states:
67 * <pre>
68 * IN_TEXT: parsing the comment text or tag text.
69 * TAG_NAME: parsing the name of a tag.
70 * TAG_GAP: skipping through the gap between the tag name and
71 * the tag text.
72 * </pre>
73 */
74 @SuppressWarnings("fallthrough")
75 class CommentStringParser {
76 /**
77 * The entry point to the comment string parser
78 */
79 void parseCommentStateMachine() {
80 final int IN_TEXT = 1;
81 final int TAG_GAP = 2;
82 final int TAG_NAME = 3;
83 int state = TAG_GAP;
84 boolean newLine = true;
85 String tagName = null;
86 int tagStart = 0;
87 int textStart = 0;
88 int lastNonWhite = -1;
89 int len = commentString.length();
90 for (int inx = 0; inx < len; ++inx) {
91 char ch = commentString.charAt(inx);
92 boolean isWhite = Character.isWhitespace(ch);
93 switch (state) {
94 case TAG_NAME:
95 if (isWhite) {
96 tagName = commentString.substring(tagStart, inx);
97 state = TAG_GAP;
98 }
99 break;
100 case TAG_GAP:
101 if (isWhite) {
102 break;
103 }
104 textStart = inx;
105 state = IN_TEXT;
106 /* fall thru */
107 case IN_TEXT:
108 if (newLine && ch == '@') {
109 parseCommentComponent(tagName, textStart,
110 lastNonWhite+1);
111 tagStart = inx;
112 state = TAG_NAME;
113 }
114 break;
115 }
116 if (ch == '\n') {
117 newLine = true;
118 } else if (!isWhite) {
119 lastNonWhite = inx;
120 newLine = false;
121 }
122 }
123 // Finish what's currently being processed
124 switch (state) {
125 case TAG_NAME:
126 tagName = commentString.substring(tagStart, len);
127 /* fall thru */
128 case TAG_GAP:
129 textStart = len;
130 /* fall thru */
131 case IN_TEXT:
132 parseCommentComponent(tagName, textStart, lastNonWhite+1);
133 break;
134 }
135 }
137 /**
138 * Save away the last parsed item.
139 */
140 void parseCommentComponent(String tagName,
141 int from, int upto) {
142 String tx = upto <= from ? "" : commentString.substring(from, upto);
143 if (tagName == null) {
144 text = tx;
145 } else {
146 TagImpl tag;
147 if (tagName.equals("@exception") || tagName.equals("@throws")) {
148 warnIfEmpty(tagName, tx);
149 tag = new ThrowsTagImpl(holder, tagName, tx);
150 } else if (tagName.equals("@param")) {
151 warnIfEmpty(tagName, tx);
152 tag = new ParamTagImpl(holder, tagName, tx);
153 } else if (tagName.equals("@see")) {
154 warnIfEmpty(tagName, tx);
155 tag = new SeeTagImpl(holder, tagName, tx);
156 } else if (tagName.equals("@serialField")) {
157 warnIfEmpty(tagName, tx);
158 tag = new SerialFieldTagImpl(holder, tagName, tx);
159 } else if (tagName.equals("@return")) {
160 warnIfEmpty(tagName, tx);
161 tag = new TagImpl(holder, tagName, tx);
162 } else if (tagName.equals("@author")) {
163 warnIfEmpty(tagName, tx);
164 tag = new TagImpl(holder, tagName, tx);
165 } else if (tagName.equals("@version")) {
166 warnIfEmpty(tagName, tx);
167 tag = new TagImpl(holder, tagName, tx);
168 } else {
169 tag = new TagImpl(holder, tagName, tx);
170 }
171 tagList.append(tag);
172 }
173 }
175 void warnIfEmpty(String tagName, String tx) {
176 if (tx.length() == 0) {
177 docenv.warning(holder, "tag.tag_has_no_arguments", tagName);
178 }
179 }
181 }
183 new CommentStringParser().parseCommentStateMachine();
184 }
186 /**
187 * Return the text of the comment.
188 */
189 String commentText() {
190 return text;
191 }
193 /**
194 * Return all tags in this comment.
195 */
196 Tag[] tags() {
197 return tagList.toArray(new Tag[tagList.length()]);
198 }
200 /**
201 * Return tags of the specified kind in this comment.
202 */
203 Tag[] tags(String tagname) {
204 ListBuffer<Tag> found = new ListBuffer<Tag>();
205 String target = tagname;
206 if (target.charAt(0) != '@') {
207 target = "@" + target;
208 }
209 for (Tag tag : tagList) {
210 if (tag.kind().equals(target)) {
211 found.append(tag);
212 }
213 }
214 return found.toArray(new Tag[found.length()]);
215 }
217 /**
218 * Return throws tags in this comment.
219 */
220 ThrowsTag[] throwsTags() {
221 ListBuffer<ThrowsTag> found = new ListBuffer<ThrowsTag>();
222 for (Tag next : tagList) {
223 if (next instanceof ThrowsTag) {
224 found.append((ThrowsTag)next);
225 }
226 }
227 return found.toArray(new ThrowsTag[found.length()]);
228 }
230 /**
231 * Return param tags (excluding type param tags) in this comment.
232 */
233 ParamTag[] paramTags() {
234 return paramTags(false);
235 }
237 /**
238 * Return type param tags in this comment.
239 */
240 ParamTag[] typeParamTags() {
241 return paramTags(true);
242 }
244 /**
245 * Return param tags in this comment. If typeParams is true
246 * include only type param tags, otherwise include only ordinary
247 * param tags.
248 */
249 private ParamTag[] paramTags(boolean typeParams) {
250 ListBuffer<ParamTag> found = new ListBuffer<ParamTag>();
251 for (Tag next : tagList) {
252 if (next instanceof ParamTag) {
253 ParamTag p = (ParamTag)next;
254 if (typeParams == p.isTypeParameter()) {
255 found.append(p);
256 }
257 }
258 }
259 return found.toArray(new ParamTag[found.length()]);
260 }
262 /**
263 * Return see also tags in this comment.
264 */
265 SeeTag[] seeTags() {
266 ListBuffer<SeeTag> found = new ListBuffer<SeeTag>();
267 for (Tag next : tagList) {
268 if (next instanceof SeeTag) {
269 found.append((SeeTag)next);
270 }
271 }
272 return found.toArray(new SeeTag[found.length()]);
273 }
275 /**
276 * Return serialField tags in this comment.
277 */
278 SerialFieldTag[] serialFieldTags() {
279 ListBuffer<SerialFieldTag> found = new ListBuffer<SerialFieldTag>();
280 for (Tag next : tagList) {
281 if (next instanceof SerialFieldTag) {
282 found.append((SerialFieldTag)next);
283 }
284 }
285 return found.toArray(new SerialFieldTag[found.length()]);
286 }
288 /**
289 * Return array of tags with text and inline See Tags for a Doc comment.
290 */
291 static Tag[] getInlineTags(DocImpl holder, String inlinetext) {
292 ListBuffer<Tag> taglist = new ListBuffer<Tag>();
293 int delimend = 0, textstart = 0, len = inlinetext.length();
294 DocEnv docenv = holder.env;
296 if (len == 0) {
297 return taglist.toArray(new Tag[taglist.length()]);
298 }
299 while (true) {
300 int linkstart;
301 if ((linkstart = inlineTagFound(holder, inlinetext,
302 textstart)) == -1) {
303 taglist.append(new TagImpl(holder, "Text",
304 inlinetext.substring(textstart)));
305 break;
306 } else {
307 int seetextstart = linkstart;
308 for (int i = linkstart; i < inlinetext.length(); i++) {
309 char c = inlinetext.charAt(i);
310 if (Character.isWhitespace(c) ||
311 c == '}') {
312 seetextstart = i;
313 break;
314 }
315 }
316 String linkName = inlinetext.substring(linkstart+2, seetextstart);
317 //Move past the white space after the inline tag name.
318 while (Character.isWhitespace(inlinetext.
319 charAt(seetextstart))) {
320 if (inlinetext.length() <= seetextstart) {
321 taglist.append(new TagImpl(holder, "Text",
322 inlinetext.substring(textstart, seetextstart)));
323 docenv.warning(holder,
324 "tag.Improper_Use_Of_Link_Tag",
325 inlinetext);
326 return taglist.toArray(new Tag[taglist.length()]);
327 } else {
328 seetextstart++;
329 }
330 }
331 taglist.append(new TagImpl(holder, "Text",
332 inlinetext.substring(textstart, linkstart)));
333 textstart = seetextstart; // this text is actually seetag
334 if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) {
335 //Missing closing '}' character.
336 // store the text as it is with the {@link.
337 taglist.append(new TagImpl(holder, "Text",
338 inlinetext.substring(textstart)));
339 docenv.warning(holder,
340 "tag.End_delimiter_missing_for_possible_SeeTag",
341 inlinetext);
342 return taglist.toArray(new Tag[taglist.length()]);
343 } else {
344 //Found closing '}' character.
345 if (linkName.equals("see")
346 || linkName.equals("link")
347 || linkName.equals("linkplain")) {
348 taglist.append( new SeeTagImpl(holder, "@" + linkName,
349 inlinetext.substring(textstart, delimend)));
350 } else {
351 taglist.append( new TagImpl(holder, "@" + linkName,
352 inlinetext.substring(textstart, delimend)));
353 }
354 textstart = delimend + 1;
355 }
356 }
357 if (textstart == inlinetext.length()) {
358 break;
359 }
360 }
361 return taglist.toArray(new Tag[taglist.length()]);
362 }
364 /**
365 * Recursively find the index of the closing '}' character for an inline tag
366 * and return it. If it can't be found, return -1.
367 * @param inlineText the text to search in.
368 * @param searchStart the index of the place to start searching at.
369 * @return the index of the closing '}' character for an inline tag.
370 * If it can't be found, return -1.
371 */
372 private static int findInlineTagDelim(String inlineText, int searchStart) {
373 int delimEnd, nestedOpenBrace;
374 if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) {
375 return -1;
376 } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) &&
377 nestedOpenBrace < delimEnd){
378 //Found a nested open brace.
379 int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1);
380 return (nestedCloseBrace != -1) ?
381 findInlineTagDelim(inlineText, nestedCloseBrace + 1) :
382 -1;
383 } else {
384 return delimEnd;
385 }
386 }
388 /**
389 * Recursively search for the string "{@" followed by
390 * name of inline tag and white space,
391 * if found
392 * return the index of the text following the white space.
393 * else
394 * return -1.
395 */
396 private static int inlineTagFound(DocImpl holder, String inlinetext, int start) {
397 DocEnv docenv = holder.env;
398 int linkstart = inlinetext.indexOf("{@", start);
399 if (start == inlinetext.length() || linkstart == -1) {
400 return -1;
401 } else if (inlinetext.indexOf('}', linkstart) == -1) {
402 //Missing '}'.
403 docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag",
404 inlinetext.substring(linkstart, inlinetext.length()));
405 return -1;
406 } else {
407 return linkstart;
408 }
409 }
412 /**
413 * Return array of tags for the locale specific first sentence in the text.
414 */
415 static Tag[] firstSentenceTags(DocImpl holder, String text) {
416 DocLocale doclocale = holder.env.doclocale;
417 return getInlineTags(holder,
418 doclocale.localeSpecificFirstSentence(holder, text));
419 }
421 /**
422 * Return text for this Doc comment.
423 */
424 @Override
425 public String toString() {
426 return text;
427 }
428 }