Thu, 18 Apr 2013 20:00:14 -0700
8012656: cache frequently used name strings for DocImpl classes
Reviewed-by: darcy
1 /*
2 * Copyright (c) 1997, 2012, 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 * <p><b>This is NOT part of any supported API.
37 * If you write code that depends on this, you do so at your own risk.
38 * This code and its internal interfaces are subject to change or
39 * deletion without notice.</b>
40 *
41 * @author Kaiyang Liu (original)
42 * @author Robert Field (rewrite)
43 * @author Atul M Dambalkar
44 * @author Neal Gafter (rewrite)
45 */
46 class Comment {
48 /**
49 * sorted comments with different tags.
50 */
51 private final ListBuffer<Tag> tagList = new ListBuffer<Tag>();
53 /**
54 * text minus any tags.
55 */
56 private String text;
58 /**
59 * Doc environment
60 */
61 private final DocEnv docenv;
63 /**
64 * constructor of Comment.
65 */
66 Comment(final DocImpl holder, final String commentString) {
67 this.docenv = holder.env;
69 /**
70 * Separate the comment into the text part and zero to N tags.
71 * Simple state machine is in one of three states:
72 * <pre>
73 * IN_TEXT: parsing the comment text or tag text.
74 * TAG_NAME: parsing the name of a tag.
75 * TAG_GAP: skipping through the gap between the tag name and
76 * the tag text.
77 * </pre>
78 */
79 @SuppressWarnings("fallthrough")
80 class CommentStringParser {
81 /**
82 * The entry point to the comment string parser
83 */
84 void parseCommentStateMachine() {
85 final int IN_TEXT = 1;
86 final int TAG_GAP = 2;
87 final int TAG_NAME = 3;
88 int state = TAG_GAP;
89 boolean newLine = true;
90 String tagName = null;
91 int tagStart = 0;
92 int textStart = 0;
93 int lastNonWhite = -1;
94 int len = commentString.length();
95 for (int inx = 0; inx < len; ++inx) {
96 char ch = commentString.charAt(inx);
97 boolean isWhite = Character.isWhitespace(ch);
98 switch (state) {
99 case TAG_NAME:
100 if (isWhite) {
101 tagName = commentString.substring(tagStart, inx);
102 state = TAG_GAP;
103 }
104 break;
105 case TAG_GAP:
106 if (isWhite) {
107 break;
108 }
109 textStart = inx;
110 state = IN_TEXT;
111 /* fall thru */
112 case IN_TEXT:
113 if (newLine && ch == '@') {
114 parseCommentComponent(tagName, textStart,
115 lastNonWhite+1);
116 tagStart = inx;
117 state = TAG_NAME;
118 }
119 break;
120 }
121 if (ch == '\n') {
122 newLine = true;
123 } else if (!isWhite) {
124 lastNonWhite = inx;
125 newLine = false;
126 }
127 }
128 // Finish what's currently being processed
129 switch (state) {
130 case TAG_NAME:
131 tagName = commentString.substring(tagStart, len);
132 /* fall thru */
133 case TAG_GAP:
134 textStart = len;
135 /* fall thru */
136 case IN_TEXT:
137 parseCommentComponent(tagName, textStart, lastNonWhite+1);
138 break;
139 }
140 }
142 /**
143 * Save away the last parsed item.
144 */
145 void parseCommentComponent(String tagName,
146 int from, int upto) {
147 String tx = upto <= from ? "" : commentString.substring(from, upto);
148 if (tagName == null) {
149 text = tx;
150 } else {
151 TagImpl tag;
152 if (tagName.equals("@exception") || tagName.equals("@throws")) {
153 warnIfEmpty(tagName, tx);
154 tag = new ThrowsTagImpl(holder, tagName, tx);
155 } else if (tagName.equals("@param")) {
156 warnIfEmpty(tagName, tx);
157 tag = new ParamTagImpl(holder, tagName, tx);
158 } else if (tagName.equals("@see")) {
159 warnIfEmpty(tagName, tx);
160 tag = new SeeTagImpl(holder, tagName, tx);
161 } else if (tagName.equals("@serialField")) {
162 warnIfEmpty(tagName, tx);
163 tag = new SerialFieldTagImpl(holder, tagName, tx);
164 } else if (tagName.equals("@return")) {
165 warnIfEmpty(tagName, tx);
166 tag = new TagImpl(holder, tagName, tx);
167 } else if (tagName.equals("@author")) {
168 warnIfEmpty(tagName, tx);
169 tag = new TagImpl(holder, tagName, tx);
170 } else if (tagName.equals("@version")) {
171 warnIfEmpty(tagName, tx);
172 tag = new TagImpl(holder, tagName, tx);
173 } else {
174 tag = new TagImpl(holder, tagName, tx);
175 }
176 tagList.append(tag);
177 }
178 }
180 void warnIfEmpty(String tagName, String tx) {
181 if (tx.length() == 0) {
182 docenv.warning(holder, "tag.tag_has_no_arguments", tagName);
183 }
184 }
186 }
188 new CommentStringParser().parseCommentStateMachine();
189 }
191 /**
192 * Return the text of the comment.
193 */
194 String commentText() {
195 return text;
196 }
198 /**
199 * Return all tags in this comment.
200 */
201 Tag[] tags() {
202 return tagList.toArray(new Tag[tagList.length()]);
203 }
205 /**
206 * Return tags of the specified kind in this comment.
207 */
208 Tag[] tags(String tagname) {
209 ListBuffer<Tag> found = new ListBuffer<Tag>();
210 String target = tagname;
211 if (target.charAt(0) != '@') {
212 target = "@" + target;
213 }
214 for (Tag tag : tagList) {
215 if (tag.kind().equals(target)) {
216 found.append(tag);
217 }
218 }
219 return found.toArray(new Tag[found.length()]);
220 }
222 /**
223 * Return throws tags in this comment.
224 */
225 ThrowsTag[] throwsTags() {
226 ListBuffer<ThrowsTag> found = new ListBuffer<ThrowsTag>();
227 for (Tag next : tagList) {
228 if (next instanceof ThrowsTag) {
229 found.append((ThrowsTag)next);
230 }
231 }
232 return found.toArray(new ThrowsTag[found.length()]);
233 }
235 /**
236 * Return param tags (excluding type param tags) in this comment.
237 */
238 ParamTag[] paramTags() {
239 return paramTags(false);
240 }
242 /**
243 * Return type param tags in this comment.
244 */
245 ParamTag[] typeParamTags() {
246 return paramTags(true);
247 }
249 /**
250 * Return param tags in this comment. If typeParams is true
251 * include only type param tags, otherwise include only ordinary
252 * param tags.
253 */
254 private ParamTag[] paramTags(boolean typeParams) {
255 ListBuffer<ParamTag> found = new ListBuffer<ParamTag>();
256 for (Tag next : tagList) {
257 if (next instanceof ParamTag) {
258 ParamTag p = (ParamTag)next;
259 if (typeParams == p.isTypeParameter()) {
260 found.append(p);
261 }
262 }
263 }
264 return found.toArray(new ParamTag[found.length()]);
265 }
267 /**
268 * Return see also tags in this comment.
269 */
270 SeeTag[] seeTags() {
271 ListBuffer<SeeTag> found = new ListBuffer<SeeTag>();
272 for (Tag next : tagList) {
273 if (next instanceof SeeTag) {
274 found.append((SeeTag)next);
275 }
276 }
277 return found.toArray(new SeeTag[found.length()]);
278 }
280 /**
281 * Return serialField tags in this comment.
282 */
283 SerialFieldTag[] serialFieldTags() {
284 ListBuffer<SerialFieldTag> found = new ListBuffer<SerialFieldTag>();
285 for (Tag next : tagList) {
286 if (next instanceof SerialFieldTag) {
287 found.append((SerialFieldTag)next);
288 }
289 }
290 return found.toArray(new SerialFieldTag[found.length()]);
291 }
293 /**
294 * Return array of tags with text and inline See Tags for a Doc comment.
295 */
296 static Tag[] getInlineTags(DocImpl holder, String inlinetext) {
297 ListBuffer<Tag> taglist = new ListBuffer<Tag>();
298 int delimend = 0, textstart = 0, len = inlinetext.length();
299 DocEnv docenv = holder.env;
301 if (len == 0) {
302 return taglist.toArray(new Tag[taglist.length()]);
303 }
304 while (true) {
305 int linkstart;
306 if ((linkstart = inlineTagFound(holder, inlinetext,
307 textstart)) == -1) {
308 taglist.append(new TagImpl(holder, "Text",
309 inlinetext.substring(textstart)));
310 break;
311 } else {
312 int seetextstart = linkstart;
313 for (int i = linkstart; i < inlinetext.length(); i++) {
314 char c = inlinetext.charAt(i);
315 if (Character.isWhitespace(c) ||
316 c == '}') {
317 seetextstart = i;
318 break;
319 }
320 }
321 String linkName = inlinetext.substring(linkstart+2, seetextstart);
322 //Move past the white space after the inline tag name.
323 while (Character.isWhitespace(inlinetext.
324 charAt(seetextstart))) {
325 if (inlinetext.length() <= seetextstart) {
326 taglist.append(new TagImpl(holder, "Text",
327 inlinetext.substring(textstart, seetextstart)));
328 docenv.warning(holder,
329 "tag.Improper_Use_Of_Link_Tag",
330 inlinetext);
331 return taglist.toArray(new Tag[taglist.length()]);
332 } else {
333 seetextstart++;
334 }
335 }
336 taglist.append(new TagImpl(holder, "Text",
337 inlinetext.substring(textstart, linkstart)));
338 textstart = seetextstart; // this text is actually seetag
339 if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) {
340 //Missing closing '}' character.
341 // store the text as it is with the {@link.
342 taglist.append(new TagImpl(holder, "Text",
343 inlinetext.substring(textstart)));
344 docenv.warning(holder,
345 "tag.End_delimiter_missing_for_possible_SeeTag",
346 inlinetext);
347 return taglist.toArray(new Tag[taglist.length()]);
348 } else {
349 //Found closing '}' character.
350 if (linkName.equals("see")
351 || linkName.equals("link")
352 || linkName.equals("linkplain")) {
353 taglist.append( new SeeTagImpl(holder, "@" + linkName,
354 inlinetext.substring(textstart, delimend)));
355 } else {
356 taglist.append( new TagImpl(holder, "@" + linkName,
357 inlinetext.substring(textstart, delimend)));
358 }
359 textstart = delimend + 1;
360 }
361 }
362 if (textstart == inlinetext.length()) {
363 break;
364 }
365 }
366 return taglist.toArray(new Tag[taglist.length()]);
367 }
369 /**
370 * Recursively find the index of the closing '}' character for an inline tag
371 * and return it. If it can't be found, return -1.
372 * @param inlineText the text to search in.
373 * @param searchStart the index of the place to start searching at.
374 * @return the index of the closing '}' character for an inline tag.
375 * If it can't be found, return -1.
376 */
377 private static int findInlineTagDelim(String inlineText, int searchStart) {
378 int delimEnd, nestedOpenBrace;
379 if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) {
380 return -1;
381 } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) &&
382 nestedOpenBrace < delimEnd){
383 //Found a nested open brace.
384 int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1);
385 return (nestedCloseBrace != -1) ?
386 findInlineTagDelim(inlineText, nestedCloseBrace + 1) :
387 -1;
388 } else {
389 return delimEnd;
390 }
391 }
393 /**
394 * Recursively search for the characters '{', '@', followed by
395 * name of inline tag and white space,
396 * if found
397 * return the index of the text following the white space.
398 * else
399 * return -1.
400 */
401 private static int inlineTagFound(DocImpl holder, String inlinetext, int start) {
402 DocEnv docenv = holder.env;
403 int linkstart = inlinetext.indexOf("{@", start);
404 if (start == inlinetext.length() || linkstart == -1) {
405 return -1;
406 } else if (inlinetext.indexOf('}', linkstart) == -1) {
407 //Missing '}'.
408 docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag",
409 inlinetext.substring(linkstart, inlinetext.length()));
410 return -1;
411 } else {
412 return linkstart;
413 }
414 }
417 /**
418 * Return array of tags for the locale specific first sentence in the text.
419 */
420 static Tag[] firstSentenceTags(DocImpl holder, String text) {
421 DocLocale doclocale = holder.env.doclocale;
422 return getInlineTags(holder,
423 doclocale.localeSpecificFirstSentence(holder, text));
424 }
426 /**
427 * Return text for this Doc comment.
428 */
429 @Override
430 public String toString() {
431 return text;
432 }
433 }