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