src/share/classes/com/sun/tools/javadoc/Comment.java

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

mercurial