001: /*
002: * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.javadoc;
027:
028: import java.util.Locale;
029:
030: import com.sun.javadoc.*;
031:
032: import com.sun.tools.javac.util.ListBuffer;
033:
034: /**
035: * Comment contains all information in comment part.
036: * It allows users to get first sentence of this comment, get
037: * comment for different tags...
038: *
039: * @author Kaiyang Liu (original)
040: * @author Robert Field (rewrite)
041: * @author Atul M Dambalkar
042: * @author Neal Gafter (rewrite)
043: */
044: class Comment {
045:
046: /**
047: * sorted comments with different tags.
048: */
049: private final ListBuffer<Tag> tagList = new ListBuffer<Tag>();
050:
051: /**
052: * text minus any tags.
053: */
054: private String text;
055:
056: /**
057: * Doc environment
058: */
059: private final DocEnv docenv;
060:
061: /**
062: * constructor of Comment.
063: */
064: Comment(final DocImpl holder, final String commentString) {
065: this .docenv = holder.env;
066:
067: /**
068: * Separate the comment into the text part and zero to N tags.
069: * Simple state machine is in one of three states:
070: * <pre>
071: * IN_TEXT: parsing the comment text or tag text.
072: * TAG_NAME: parsing the name of a tag.
073: * TAG_GAP: skipping through the gap between the tag name and
074: * the tag text.
075: * </pre>
076: */
077: class CommentStringParser {
078: /**
079: * The entry point to the comment string parser
080: */
081: void parseCommentStateMachine() {
082: final int IN_TEXT = 1;
083: final int TAG_GAP = 2;
084: final int TAG_NAME = 3;
085: int state = TAG_GAP;
086: boolean newLine = true;
087: String tagName = null;
088: int tagStart = 0;
089: int textStart = 0;
090: int lastNonWhite = -1;
091: int len = commentString.length();
092: for (int inx = 0; inx < len; ++inx) {
093: char ch = commentString.charAt(inx);
094: boolean isWhite = Character.isWhitespace(ch);
095: switch (state) {
096: case TAG_NAME:
097: if (isWhite) {
098: tagName = commentString.substring(tagStart,
099: inx);
100: state = TAG_GAP;
101: }
102: break;
103: case TAG_GAP:
104: if (isWhite) {
105: break;
106: }
107: textStart = inx;
108: state = IN_TEXT;
109: /* fall thru */
110: case IN_TEXT:
111: if (newLine && ch == '@') {
112: parseCommentComponent(tagName, textStart,
113: lastNonWhite + 1);
114: tagStart = inx;
115: state = TAG_NAME;
116: }
117: break;
118: }
119: ;
120: if (ch == '\n') {
121: newLine = true;
122: } else if (!isWhite) {
123: lastNonWhite = inx;
124: newLine = false;
125: }
126: }
127: // Finish what's currently being processed
128: switch (state) {
129: case TAG_NAME:
130: tagName = commentString.substring(tagStart, len);
131: /* fall thru */
132: case TAG_GAP:
133: textStart = len;
134: /* fall thru */
135: case IN_TEXT:
136: parseCommentComponent(tagName, textStart,
137: lastNonWhite + 1);
138: break;
139: }
140: ;
141: }
142:
143: /**
144: * Save away the last parsed item.
145: */
146: void parseCommentComponent(String tagName, int from,
147: int upto) {
148: String tx = upto <= from ? "" : commentString
149: .substring(from, upto);
150: if (tagName == null) {
151: text = tx;
152: } else {
153: TagImpl tag;
154: if (tagName.equals("@exception")
155: || tagName.equals("@throws")) {
156: warnIfEmpty(tagName, tx);
157: tag = new ThrowsTagImpl(holder, tagName, tx);
158: } else if (tagName.equals("@param")) {
159: warnIfEmpty(tagName, tx);
160: tag = new ParamTagImpl(holder, tagName, tx);
161: } else if (tagName.equals("@see")) {
162: warnIfEmpty(tagName, tx);
163: tag = new SeeTagImpl(holder, tagName, tx);
164: } else if (tagName.equals("@serialField")) {
165: warnIfEmpty(tagName, tx);
166: tag = new SerialFieldTagImpl(holder, tagName,
167: tx);
168: } else if (tagName.equals("@return")) {
169: warnIfEmpty(tagName, tx);
170: tag = new TagImpl(holder, tagName, tx);
171: } else if (tagName.equals("@author")) {
172: warnIfEmpty(tagName, tx);
173: tag = new TagImpl(holder, tagName, tx);
174: } else if (tagName.equals("@version")) {
175: warnIfEmpty(tagName, tx);
176: tag = new TagImpl(holder, tagName, tx);
177: } else {
178: tag = new TagImpl(holder, tagName, tx);
179: }
180: tagList.append(tag);
181: }
182: }
183:
184: void warnIfEmpty(String tagName, String tx) {
185: if (tx.length() == 0) {
186: docenv.warning(holder, "tag.tag_has_no_arguments",
187: tagName);
188: }
189: }
190:
191: }
192:
193: new CommentStringParser().parseCommentStateMachine();
194: }
195:
196: /**
197: * Return the text of the comment.
198: */
199: String commentText() {
200: return text;
201: }
202:
203: /**
204: * Return all tags in this comment.
205: */
206: Tag[] tags() {
207: return tagList.toArray(new Tag[tagList.length()]);
208: }
209:
210: /**
211: * Return tags of the specified kind in this comment.
212: */
213: Tag[] tags(String tagname) {
214: ListBuffer<Tag> found = new ListBuffer<Tag>();
215: String target = tagname;
216: if (target.charAt(0) != '@') {
217: target = "@" + target;
218: }
219: for (Tag tag : tagList) {
220: if (tag.kind().equals(target)) {
221: found.append(tag);
222: }
223: }
224: return found.toArray(new Tag[found.length()]);
225: }
226:
227: /**
228: * Return throws tags in this comment.
229: */
230: ThrowsTag[] throwsTags() {
231: ListBuffer<ThrowsTag> found = new ListBuffer<ThrowsTag>();
232: for (Tag next : tagList) {
233: if (next instanceof ThrowsTag) {
234: found.append((ThrowsTag) next);
235: }
236: }
237: return found.toArray(new ThrowsTag[found.length()]);
238: }
239:
240: /**
241: * Return param tags (excluding type param tags) in this comment.
242: */
243: ParamTag[] paramTags() {
244: return paramTags(false);
245: }
246:
247: /**
248: * Return type param tags in this comment.
249: */
250: ParamTag[] typeParamTags() {
251: return paramTags(true);
252: }
253:
254: /**
255: * Return param tags in this comment. If typeParams is true
256: * include only type param tags, otherwise include only ordinary
257: * param tags.
258: */
259: private ParamTag[] paramTags(boolean typeParams) {
260: ListBuffer<ParamTag> found = new ListBuffer<ParamTag>();
261: for (Tag next : tagList) {
262: if (next instanceof ParamTag) {
263: ParamTag p = (ParamTag) next;
264: if (typeParams == p.isTypeParameter()) {
265: found.append(p);
266: }
267: }
268: }
269: return found.toArray(new ParamTag[found.length()]);
270: }
271:
272: /**
273: * Return see also tags in this comment.
274: */
275: SeeTag[] seeTags() {
276: ListBuffer<SeeTag> found = new ListBuffer<SeeTag>();
277: for (Tag next : tagList) {
278: if (next instanceof SeeTag) {
279: found.append((SeeTag) next);
280: }
281: }
282: return found.toArray(new SeeTag[found.length()]);
283: }
284:
285: /**
286: * Return serialField tags in this comment.
287: */
288: SerialFieldTag[] serialFieldTags() {
289: ListBuffer<SerialFieldTag> found = new ListBuffer<SerialFieldTag>();
290: for (Tag next : tagList) {
291: if (next instanceof SerialFieldTag) {
292: found.append((SerialFieldTag) next);
293: }
294: }
295: return found.toArray(new SerialFieldTag[found.length()]);
296: }
297:
298: /**
299: * Return array of tags with text and inline See Tags for a Doc comment.
300: */
301: static Tag[] getInlineTags(DocImpl holder, String inlinetext) {
302: ListBuffer<Tag> taglist = new ListBuffer<Tag>();
303: int delimend = 0, textstart = 0, len = inlinetext.length();
304: DocEnv docenv = holder.env;
305:
306: if (len == 0) {
307: return taglist.toArray(new Tag[taglist.length()]);
308: }
309: while (true) {
310: int linkstart;
311: if ((linkstart = inlineTagFound(holder, inlinetext,
312: textstart)) == -1) {
313: taglist.append(new TagImpl(holder, "Text", inlinetext
314: .substring(textstart)));
315: break;
316: } else {
317: int seetextstart = linkstart;
318: for (int i = linkstart; i < inlinetext.length(); i++) {
319: char c = inlinetext.charAt(i);
320: if (Character.isWhitespace(c) || c == '}') {
321: seetextstart = i;
322: break;
323: }
324: }
325: String linkName = inlinetext.substring(linkstart + 2,
326: seetextstart);
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,
333: seetextstart)));
334: docenv.warning(holder,
335: "tag.Improper_Use_Of_Link_Tag",
336: inlinetext);
337: return taglist
338: .toArray(new Tag[taglist.length()]);
339: } else {
340: seetextstart++;
341: }
342: }
343: taglist.append(new TagImpl(holder, "Text", inlinetext
344: .substring(textstart, linkstart)));
345: textstart = seetextstart; // this text is actually seetag
346: if ((delimend = findInlineTagDelim(inlinetext,
347: textstart)) == -1) {
348: //Missing closing '}' character.
349: // store the text as it is with the {@link.
350: taglist.append(new TagImpl(holder, "Text",
351: inlinetext.substring(textstart)));
352: docenv
353: .warning(
354: holder,
355: "tag.End_delimiter_missing_for_possible_SeeTag",
356: inlinetext);
357: return taglist.toArray(new Tag[taglist.length()]);
358: } else {
359: //Found closing '}' character.
360: if (linkName.equals("see")
361: || linkName.equals("link")
362: || linkName.equals("linkplain")) {
363: taglist.append(new SeeTagImpl(holder, "@"
364: + linkName, inlinetext.substring(
365: textstart, delimend)));
366: } else {
367: taglist.append(new TagImpl(holder, "@"
368: + linkName, inlinetext.substring(
369: textstart, delimend)));
370: }
371: textstart = delimend + 1;
372: }
373: }
374: if (textstart == inlinetext.length()) {
375: break;
376: }
377: }
378: return taglist.toArray(new Tag[taglist.length()]);
379: }
380:
381: /**
382: * Recursively find the index of the closing '}' character for an inline tag
383: * and return it. If it can't be found, return -1.
384: * @param inlineText the text to search in.
385: * @param searchStart the index of the place to start searching at.
386: * @return the index of the closing '}' character for an inline tag.
387: * If it can't be found, return -1.
388: */
389: private static int findInlineTagDelim(String inlineText,
390: int searchStart) {
391: int delimEnd, nestedOpenBrace;
392: if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) {
393: return -1;
394: } else if (((nestedOpenBrace = inlineText.indexOf("{",
395: searchStart)) != -1)
396: && nestedOpenBrace < delimEnd) {
397: //Found a nested open brace.
398: int nestedCloseBrace = findInlineTagDelim(inlineText,
399: nestedOpenBrace + 1);
400: return (nestedCloseBrace != -1) ? findInlineTagDelim(
401: inlineText, nestedCloseBrace + 1) : -1;
402: } else {
403: return delimEnd;
404: }
405: }
406:
407: /**
408: * Recursively search for the string "{@" followed by
409: * name of inline tag and white space,
410: * if found
411: * return the index of the text following the white space.
412: * else
413: * return -1.
414: */
415: private static int inlineTagFound(DocImpl holder,
416: String inlinetext, int start) {
417: DocEnv docenv = holder.env;
418: int linkstart;
419: if (start == inlinetext.length()
420: || (linkstart = inlinetext.indexOf("{@", start)) == -1) {
421: return -1;
422: } else if (inlinetext.indexOf('}', start) == -1) {
423: //Missing '}'.
424: docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag",
425: inlinetext
426: .substring(linkstart, inlinetext.length()));
427: return -1;
428: } else {
429: return linkstart;
430: }
431: }
432:
433: /**
434: * Return array of tags for the locale specific first sentence in the text.
435: */
436: static Tag[] firstSentenceTags(DocImpl holder, String text) {
437: DocLocale doclocale = holder.env.doclocale;
438: return getInlineTags(holder, doclocale
439: .localeSpecificFirstSentence(holder, text));
440: }
441:
442: /**
443: * Return text for this Doc comment.
444: */
445: public String toString() {
446: return text;
447: }
448: }
|