/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.ArrayList; public class Comment { static final Pattern LEADING_WHITESPACE = Pattern.compile( "^[ \t\n\r]*(.*)$", Pattern.DOTALL); static final Pattern TAG_BEGIN = Pattern.compile( "[\r\n][\r\n \t]*@", Pattern.DOTALL); static final Pattern TAG = Pattern.compile( "(@[^ \t\r\n]+)[ \t\r\n]+(.*)", Pattern.DOTALL); static final Pattern INLINE_TAG = Pattern.compile( "(.*?)\\{(@[^ \t\r\n\\}]+)[ \t\r\n]*(.*?)\\}", Pattern.DOTALL); static final Pattern FIRST_SENTENCE = Pattern.compile( "((.*?)\\.)[ \t\r\n\\<](.*)", Pattern.DOTALL); private static final String[] KNOWN_TAGS = new String[] { "@author", "@since", "@version", "@deprecated", "@undeprecate", "@docRoot", "@sdkCurrent", "@inheritDoc", "@more", "@code", "@samplecode", "@sample", "@include", "@serial", }; public Comment(String text, ContainerInfo base, SourcePositionInfo sp) { mText = text; mBase = base; // sp now points to the end of the text, not the beginning! mPosition = SourcePositionInfo.findBeginning(sp, text); } private void parseRegex(String text) { Matcher m; m = LEADING_WHITESPACE.matcher(text); m.matches(); text = m.group(1); m = TAG_BEGIN.matcher(text); int start = 0; int end = 0; while (m.find()) { end = m.start(); tag(text, start, end); start = m.end()-1; // -1 is the @ } end = text.length(); tag(text, start, end); } private void tag(String text, int start, int end) { SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, start); if (start >= 0 && end > 0 && (end-start) > 0) { text = text.substring(start, end); Matcher m = TAG.matcher(text); if (m.matches()) { // out of line tag tag(m.group(1), m.group(2), false, pos); } else { // look for inline tags m = INLINE_TAG.matcher(text); start = 0; while (m.find()) { String str = m.group(1); String tagname = m.group(2); String tagvalue = m.group(3); tag(null, m.group(1), true, pos); tag(tagname, tagvalue, true, pos); start = m.end(); } int len = text.length(); if (start != len) { tag(null, text.substring(start), true, pos); } } } } private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) { /* String s = isInline ? "inline" : "outofline"; System.out.println("---> " + s + " name=[" + name + "] text=[" + text + "]"); */ if (name == null) { mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos)); } else if (name.equals("@param")) { mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos)); } else if (name.equals("@see")) { mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos)); } else if (name.equals("@link") || name.equals("@linkplain")) { mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos)); } else if (name.equals("@throws") || name.equals("@exception")) { mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos)); } else if (name.equals("@return")) { mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos)); } else if (name.equals("@deprecated")) { if (text.length() == 0) { Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment"); text = "No replacement."; } mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos)); } else if (name.equals("@literal")) { mInlineTagsList.add(new LiteralTagInfo(name, name, text, pos)); } else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) { // nothing } else if (name.equals("@attr")) { AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos); mAttrTagsList.add(tag); Comment c = tag.description(); if (c != null) { for (TagInfo t: c.tags()) { mInlineTagsList.add(t); } } } else if (name.equals("@undeprecate")) { mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos)); } else if (name.equals("@include") || name.equals("@sample")) { mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos)); } else { boolean known = false; for (String s: KNOWN_TAGS) { if (s.equals(name)) { known = true; break; } } if (!known) { known = DroidDoc.knownTags.contains(name); } if (!known) { Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos), "Unknown tag: " + name); } TagInfo t = new TextTagInfo(name, name, text, pos); if (isInline) { mInlineTagsList.add(t); } else { mTagsList.add(t); } } } private void parseBriefTags() { int N = mInlineTagsList.size(); // look for "@more" tag, which means that we might go past the first sentence. int more = -1; for (int i=0; i<N; i++) { if (mInlineTagsList.get(i).name().equals("@more")) { more = i; } } if (more >= 0) { for (int i=0; i<more; i++) { mBriefTagsList.add(mInlineTagsList.get(i)); } } else { for (int i=0; i<N; i++) { TagInfo t = mInlineTagsList.get(i); if (t.name().equals("Text")) { Matcher m = FIRST_SENTENCE.matcher(t.text()); if (m.matches()) { String text = m.group(1); TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position()); mBriefTagsList.add(firstSentenceTag); break; } } mBriefTagsList.add(t); } } } public TagInfo[] tags() { init(); return mInlineTags; } public TagInfo[] tags(String name) { init(); ArrayList<TagInfo> results = new ArrayList<TagInfo>(); int N = mInlineTagsList.size(); for (int i=0; i<N; i++) { TagInfo t = mInlineTagsList.get(i); if (t.name().equals(name)) { results.add(t); } } return results.toArray(new TagInfo[results.size()]); } public ParamTagInfo[] paramTags() { init(); return mParamTags; } public SeeTagInfo[] seeTags() { init(); return mSeeTags; } public ThrowsTagInfo[] throwsTags() { init(); return mThrowsTags; } public TagInfo[] returnTags() { init(); return mReturnTags; } public TagInfo[] deprecatedTags() { init(); return mDeprecatedTags; } public TagInfo[] undeprecateTags() { init(); return mUndeprecateTags; } public AttrTagInfo[] attrTags() { init(); return mAttrTags; } public TagInfo[] briefTags() { init(); return mBriefTags; } public boolean isHidden() { if (mHidden >= 0) { return mHidden != 0; } else { if (DroidDoc.checkLevel(DroidDoc.SHOW_HIDDEN)) { mHidden = 0; return false; } boolean b = mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0; mHidden = b ? 1 : 0; return b; } } public boolean isDocOnly() { if (mDocOnly >= 0) { return mDocOnly != 0; } else { boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0); mDocOnly = b ? 1 : 0; return b; } } private void init() { if (!mInitialized) { initImpl(); } } private void initImpl() { isHidden(); isDocOnly(); // Don't bother parsing text if we aren't generating documentation. if (DroidDoc.parseComments()) { parseRegex(mText); parseBriefTags(); } else { // Forces methods to be recognized by findOverriddenMethods in MethodInfo. mInlineTagsList.add(new TextTagInfo("Text", "Text", mText, SourcePositionInfo.add(mPosition, mText, 0))); } mText = null; mInitialized = true; mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]); mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]); mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]); mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]); mReturnTags = ParsedTagInfo.joinTags(mReturnTagsList.toArray( new ParsedTagInfo[mReturnTagsList.size()])); mDeprecatedTags = ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray( new ParsedTagInfo[mDeprecatedTagsList.size()])); mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]); mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]); mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]); mParamTagsList = null; mSeeTagsList = null; mThrowsTagsList = null; mReturnTagsList = null; mDeprecatedTagsList = null; mUndeprecateTagsList = null; mAttrTagsList = null; mBriefTagsList = null; } boolean mInitialized; int mHidden = -1; int mDocOnly = -1; String mText; ContainerInfo mBase; SourcePositionInfo mPosition; int mLine = 1; TagInfo[] mInlineTags; TagInfo[] mTags; ParamTagInfo[] mParamTags; SeeTagInfo[] mSeeTags; ThrowsTagInfo[] mThrowsTags; TagInfo[] mBriefTags; TagInfo[] mReturnTags; TagInfo[] mDeprecatedTags; TagInfo[] mUndeprecateTags; AttrTagInfo[] mAttrTags; ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>(); ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>(); ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>(); ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>(); ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>(); ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>(); ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>(); ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>(); ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>(); ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>(); }