| /* |
| * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| */ |
| |
| package com.sun.tools.javadoc; |
| |
| import java.util.Locale; |
| |
| import com.sun.javadoc.*; |
| |
| import com.sun.tools.javac.util.ListBuffer; |
| |
| /** |
| * Comment contains all information in comment part. |
| * It allows users to get first sentence of this comment, get |
| * comment for different tags... |
| * |
| * @author Kaiyang Liu (original) |
| * @author Robert Field (rewrite) |
| * @author Atul M Dambalkar |
| * @author Neal Gafter (rewrite) |
| */ |
| class Comment { |
| |
| /** |
| * sorted comments with different tags. |
| */ |
| private final ListBuffer<Tag> tagList = new ListBuffer<Tag>(); |
| |
| /** |
| * text minus any tags. |
| */ |
| private String text; |
| |
| /** |
| * Doc environment |
| */ |
| private final DocEnv docenv; |
| |
| /** |
| * constructor of Comment. |
| */ |
| Comment(final DocImpl holder, final String commentString) { |
| this.docenv = holder.env; |
| |
| /** |
| * Separate the comment into the text part and zero to N tags. |
| * Simple state machine is in one of three states: |
| * <pre> |
| * IN_TEXT: parsing the comment text or tag text. |
| * TAG_NAME: parsing the name of a tag. |
| * TAG_GAP: skipping through the gap between the tag name and |
| * the tag text. |
| * </pre> |
| */ |
| class CommentStringParser { |
| /** |
| * The entry point to the comment string parser |
| */ |
| void parseCommentStateMachine() { |
| final int IN_TEXT = 1; |
| final int TAG_GAP = 2; |
| final int TAG_NAME = 3; |
| int state = TAG_GAP; |
| boolean newLine = true; |
| String tagName = null; |
| int tagStart = 0; |
| int textStart = 0; |
| int lastNonWhite = -1; |
| int len = commentString.length(); |
| for (int inx = 0; inx < len; ++inx) { |
| char ch = commentString.charAt(inx); |
| boolean isWhite = Character.isWhitespace(ch); |
| switch (state) { |
| case TAG_NAME: |
| if (isWhite) { |
| tagName = commentString.substring(tagStart, inx); |
| state = TAG_GAP; |
| } |
| break; |
| case TAG_GAP: |
| if (isWhite) { |
| break; |
| } |
| textStart = inx; |
| state = IN_TEXT; |
| /* fall thru */ |
| case IN_TEXT: |
| if (newLine && ch == '@') { |
| parseCommentComponent(tagName, textStart, |
| lastNonWhite+1); |
| tagStart = inx; |
| state = TAG_NAME; |
| } |
| break; |
| }; |
| if (ch == '\n') { |
| newLine = true; |
| } else if (!isWhite) { |
| lastNonWhite = inx; |
| newLine = false; |
| } |
| } |
| // Finish what's currently being processed |
| switch (state) { |
| case TAG_NAME: |
| tagName = commentString.substring(tagStart, len); |
| /* fall thru */ |
| case TAG_GAP: |
| textStart = len; |
| /* fall thru */ |
| case IN_TEXT: |
| parseCommentComponent(tagName, textStart, lastNonWhite+1); |
| break; |
| }; |
| } |
| |
| /** |
| * Save away the last parsed item. |
| */ |
| void parseCommentComponent(String tagName, |
| int from, int upto) { |
| String tx = upto <= from ? "" : commentString.substring(from, upto); |
| if (tagName == null) { |
| text = tx; |
| } else { |
| TagImpl tag; |
| if (tagName.equals("@exception") || tagName.equals("@throws")) { |
| warnIfEmpty(tagName, tx); |
| tag = new ThrowsTagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@param")) { |
| warnIfEmpty(tagName, tx); |
| tag = new ParamTagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@see")) { |
| warnIfEmpty(tagName, tx); |
| tag = new SeeTagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@serialField")) { |
| warnIfEmpty(tagName, tx); |
| tag = new SerialFieldTagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@return")) { |
| warnIfEmpty(tagName, tx); |
| tag = new TagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@author")) { |
| warnIfEmpty(tagName, tx); |
| tag = new TagImpl(holder, tagName, tx); |
| } else if (tagName.equals("@version")) { |
| warnIfEmpty(tagName, tx); |
| tag = new TagImpl(holder, tagName, tx); |
| } else { |
| tag = new TagImpl(holder, tagName, tx); |
| } |
| tagList.append(tag); |
| } |
| } |
| |
| void warnIfEmpty(String tagName, String tx) { |
| if (tx.length() == 0) { |
| docenv.warning(holder, "tag.tag_has_no_arguments", tagName); |
| } |
| } |
| |
| } |
| |
| new CommentStringParser().parseCommentStateMachine(); |
| } |
| |
| /** |
| * Return the text of the comment. |
| */ |
| String commentText() { |
| return text; |
| } |
| |
| /** |
| * Return all tags in this comment. |
| */ |
| Tag[] tags() { |
| return tagList.toArray(new Tag[tagList.length()]); |
| } |
| |
| /** |
| * Return tags of the specified kind in this comment. |
| */ |
| Tag[] tags(String tagname) { |
| ListBuffer<Tag> found = new ListBuffer<Tag>(); |
| String target = tagname; |
| if (target.charAt(0) != '@') { |
| target = "@" + target; |
| } |
| for (Tag tag : tagList) { |
| if (tag.kind().equals(target)) { |
| found.append(tag); |
| } |
| } |
| return found.toArray(new Tag[found.length()]); |
| } |
| |
| /** |
| * Return throws tags in this comment. |
| */ |
| ThrowsTag[] throwsTags() { |
| ListBuffer<ThrowsTag> found = new ListBuffer<ThrowsTag>(); |
| for (Tag next : tagList) { |
| if (next instanceof ThrowsTag) { |
| found.append((ThrowsTag)next); |
| } |
| } |
| return found.toArray(new ThrowsTag[found.length()]); |
| } |
| |
| /** |
| * Return param tags (excluding type param tags) in this comment. |
| */ |
| ParamTag[] paramTags() { |
| return paramTags(false); |
| } |
| |
| /** |
| * Return type param tags in this comment. |
| */ |
| ParamTag[] typeParamTags() { |
| return paramTags(true); |
| } |
| |
| /** |
| * Return param tags in this comment. If typeParams is true |
| * include only type param tags, otherwise include only ordinary |
| * param tags. |
| */ |
| private ParamTag[] paramTags(boolean typeParams) { |
| ListBuffer<ParamTag> found = new ListBuffer<ParamTag>(); |
| for (Tag next : tagList) { |
| if (next instanceof ParamTag) { |
| ParamTag p = (ParamTag)next; |
| if (typeParams == p.isTypeParameter()) { |
| found.append(p); |
| } |
| } |
| } |
| return found.toArray(new ParamTag[found.length()]); |
| } |
| |
| /** |
| * Return see also tags in this comment. |
| */ |
| SeeTag[] seeTags() { |
| ListBuffer<SeeTag> found = new ListBuffer<SeeTag>(); |
| for (Tag next : tagList) { |
| if (next instanceof SeeTag) { |
| found.append((SeeTag)next); |
| } |
| } |
| return found.toArray(new SeeTag[found.length()]); |
| } |
| |
| /** |
| * Return serialField tags in this comment. |
| */ |
| SerialFieldTag[] serialFieldTags() { |
| ListBuffer<SerialFieldTag> found = new ListBuffer<SerialFieldTag>(); |
| for (Tag next : tagList) { |
| if (next instanceof SerialFieldTag) { |
| found.append((SerialFieldTag)next); |
| } |
| } |
| return found.toArray(new SerialFieldTag[found.length()]); |
| } |
| |
| /** |
| * Return array of tags with text and inline See Tags for a Doc comment. |
| */ |
| static Tag[] getInlineTags(DocImpl holder, String inlinetext) { |
| ListBuffer<Tag> taglist = new ListBuffer<Tag>(); |
| int delimend = 0, textstart = 0, len = inlinetext.length(); |
| DocEnv docenv = holder.env; |
| |
| if (len == 0) { |
| return taglist.toArray(new Tag[taglist.length()]); |
| } |
| while (true) { |
| int linkstart; |
| if ((linkstart = inlineTagFound(holder, inlinetext, |
| textstart)) == -1) { |
| taglist.append(new TagImpl(holder, "Text", |
| inlinetext.substring(textstart))); |
| break; |
| } else { |
| int seetextstart = linkstart; |
| for (int i = linkstart; i < inlinetext.length(); i++) { |
| char c = inlinetext.charAt(i); |
| if (Character.isWhitespace(c) || |
| c == '}') { |
| seetextstart = i; |
| break; |
| } |
| } |
| String linkName = inlinetext.substring(linkstart+2, seetextstart); |
| //Move past the white space after the inline tag name. |
| while (Character.isWhitespace(inlinetext. |
| charAt(seetextstart))) { |
| if (inlinetext.length() <= seetextstart) { |
| taglist.append(new TagImpl(holder, "Text", |
| inlinetext.substring(textstart, seetextstart))); |
| docenv.warning(holder, |
| "tag.Improper_Use_Of_Link_Tag", |
| inlinetext); |
| return taglist.toArray(new Tag[taglist.length()]); |
| } else { |
| seetextstart++; |
| } |
| } |
| taglist.append(new TagImpl(holder, "Text", |
| inlinetext.substring(textstart, linkstart))); |
| textstart = seetextstart; // this text is actually seetag |
| if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) { |
| //Missing closing '}' character. |
| // store the text as it is with the {@link. |
| taglist.append(new TagImpl(holder, "Text", |
| inlinetext.substring(textstart))); |
| docenv.warning(holder, |
| "tag.End_delimiter_missing_for_possible_SeeTag", |
| inlinetext); |
| return taglist.toArray(new Tag[taglist.length()]); |
| } else { |
| //Found closing '}' character. |
| if (linkName.equals("see") |
| || linkName.equals("link") |
| || linkName.equals("linkplain")) { |
| taglist.append( new SeeTagImpl(holder, "@" + linkName, |
| inlinetext.substring(textstart, delimend))); |
| } else { |
| taglist.append( new TagImpl(holder, "@" + linkName, |
| inlinetext.substring(textstart, delimend))); |
| } |
| textstart = delimend + 1; |
| } |
| } |
| if (textstart == inlinetext.length()) { |
| break; |
| } |
| } |
| return taglist.toArray(new Tag[taglist.length()]); |
| } |
| |
| /** |
| * Recursively find the index of the closing '}' character for an inline tag |
| * and return it. If it can't be found, return -1. |
| * @param inlineText the text to search in. |
| * @param searchStart the index of the place to start searching at. |
| * @return the index of the closing '}' character for an inline tag. |
| * If it can't be found, return -1. |
| */ |
| private static int findInlineTagDelim(String inlineText, int searchStart) { |
| int delimEnd, nestedOpenBrace; |
| if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) { |
| return -1; |
| } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) && |
| nestedOpenBrace < delimEnd){ |
| //Found a nested open brace. |
| int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1); |
| return (nestedCloseBrace != -1) ? |
| findInlineTagDelim(inlineText, nestedCloseBrace + 1) : |
| -1; |
| } else { |
| return delimEnd; |
| } |
| } |
| |
| /** |
| * Recursively search for the string "{@" followed by |
| * name of inline tag and white space, |
| * if found |
| * return the index of the text following the white space. |
| * else |
| * return -1. |
| */ |
| private static int inlineTagFound(DocImpl holder, String inlinetext, int start) { |
| DocEnv docenv = holder.env; |
| int linkstart; |
| if (start == inlinetext.length() || |
| (linkstart = inlinetext.indexOf("{@", start)) == -1) { |
| return -1; |
| } else if(inlinetext.indexOf('}', start) == -1) { |
| //Missing '}'. |
| docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag", |
| inlinetext.substring(linkstart, inlinetext.length())); |
| return -1; |
| } else { |
| return linkstart; |
| } |
| } |
| |
| |
| /** |
| * Return array of tags for the locale specific first sentence in the text. |
| */ |
| static Tag[] firstSentenceTags(DocImpl holder, String text) { |
| DocLocale doclocale = holder.env.doclocale; |
| return getInlineTags(holder, |
| doclocale.localeSpecificFirstSentence(holder, text)); |
| } |
| |
| /** |
| * Return text for this Doc comment. |
| */ |
| public String toString() { |
| return text; |
| } |
| } |