/* * Copyright (c) 1997, 2003, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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; } }