1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import java.util.regex.Pattern; 18 import java.util.regex.Matcher; 19 import java.util.ArrayList; 20 21 public class Comment 22 { 23 static final Pattern LEADING_WHITESPACE = Pattern.compile( 24 "^[ \t\n\r]*(.*)$", 25 Pattern.DOTALL); 26 27 static final Pattern TAG_BEGIN = Pattern.compile( 28 "[\r\n][\r\n \t]*@", 29 Pattern.DOTALL); 30 31 static final Pattern TAG = Pattern.compile( 32 "(@[^ \t\r\n]+)[ \t\r\n]+(.*)", 33 Pattern.DOTALL); 34 35 static final Pattern INLINE_TAG = Pattern.compile( 36 "(.*?)\\{(@[^ \t\r\n\\}]+)[ \t\r\n]*(.*?)\\}", 37 Pattern.DOTALL); 38 39 static final Pattern FIRST_SENTENCE = Pattern.compile( 40 "((.*?)\\.)[ \t\r\n\\<](.*)", 41 Pattern.DOTALL); 42 43 private static final String[] KNOWN_TAGS = new String[] { 44 "@author", 45 "@since", 46 "@version", 47 "@deprecated", 48 "@undeprecate", 49 "@docRoot", 50 "@sdkCurrent", 51 "@inheritDoc", 52 "@more", 53 "@code", 54 "@samplecode", 55 "@sample", 56 "@include", 57 "@serial", 58 }; 59 Comment(String text, ContainerInfo base, SourcePositionInfo sp)60 public Comment(String text, ContainerInfo base, SourcePositionInfo sp) 61 { 62 mText = text; 63 mBase = base; 64 // sp now points to the end of the text, not the beginning! 65 mPosition = SourcePositionInfo.findBeginning(sp, text); 66 } 67 parseRegex(String text)68 private void parseRegex(String text) 69 { 70 Matcher m; 71 72 m = LEADING_WHITESPACE.matcher(text); 73 m.matches(); 74 text = m.group(1); 75 76 m = TAG_BEGIN.matcher(text); 77 78 int start = 0; 79 int end = 0; 80 while (m.find()) { 81 end = m.start(); 82 83 tag(text, start, end); 84 85 start = m.end()-1; // -1 is the @ 86 } 87 end = text.length(); 88 tag(text, start, end); 89 } 90 tag(String text, int start, int end)91 private void tag(String text, int start, int end) 92 { 93 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, start); 94 95 if (start >= 0 && end > 0 && (end-start) > 0) { 96 text = text.substring(start, end); 97 98 Matcher m = TAG.matcher(text); 99 if (m.matches()) { 100 // out of line tag 101 tag(m.group(1), m.group(2), false, pos); 102 } else { 103 // look for inline tags 104 m = INLINE_TAG.matcher(text); 105 start = 0; 106 while (m.find()) { 107 String str = m.group(1); 108 String tagname = m.group(2); 109 String tagvalue = m.group(3); 110 tag(null, m.group(1), true, pos); 111 tag(tagname, tagvalue, true, pos); 112 start = m.end(); 113 } 114 int len = text.length(); 115 if (start != len) { 116 tag(null, text.substring(start), true, pos); 117 } 118 } 119 } 120 } 121 tag(String name, String text, boolean isInline, SourcePositionInfo pos)122 private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) 123 { 124 /* 125 String s = isInline ? "inline" : "outofline"; 126 System.out.println("---> " + s 127 + " name=[" + name + "] text=[" + text + "]"); 128 */ 129 if (name == null) { 130 mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos)); 131 } 132 else if (name.equals("@param")) { 133 mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos)); 134 } 135 else if (name.equals("@see")) { 136 mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos)); 137 } 138 else if (name.equals("@link") || name.equals("@linkplain")) { 139 mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos)); 140 } 141 else if (name.equals("@throws") || name.equals("@exception")) { 142 mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos)); 143 } 144 else if (name.equals("@return")) { 145 mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos)); 146 } 147 else if (name.equals("@deprecated")) { 148 if (text.length() == 0) { 149 Errors.error(Errors.MISSING_COMMENT, pos, 150 "@deprecated tag with no explanatory comment"); 151 text = "No replacement."; 152 } 153 mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos)); 154 } 155 else if (name.equals("@literal")) { 156 mInlineTagsList.add(new LiteralTagInfo(name, name, text, pos)); 157 } 158 else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) { 159 // nothing 160 } 161 else if (name.equals("@attr")) { 162 AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos); 163 mAttrTagsList.add(tag); 164 Comment c = tag.description(); 165 if (c != null) { 166 for (TagInfo t: c.tags()) { 167 mInlineTagsList.add(t); 168 } 169 } 170 } 171 else if (name.equals("@undeprecate")) { 172 mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos)); 173 } 174 else if (name.equals("@include") || name.equals("@sample")) { 175 mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos)); 176 } 177 else { 178 boolean known = false; 179 for (String s: KNOWN_TAGS) { 180 if (s.equals(name)) { 181 known = true; 182 break; 183 } 184 } 185 if (!known) { 186 known = DroidDoc.knownTags.contains(name); 187 } 188 if (!known) { 189 Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos), 190 "Unknown tag: " + name); 191 } 192 TagInfo t = new TextTagInfo(name, name, text, pos); 193 if (isInline) { 194 mInlineTagsList.add(t); 195 } else { 196 mTagsList.add(t); 197 } 198 } 199 } 200 parseBriefTags()201 private void parseBriefTags() 202 { 203 int N = mInlineTagsList.size(); 204 205 // look for "@more" tag, which means that we might go past the first sentence. 206 int more = -1; 207 for (int i=0; i<N; i++) { 208 if (mInlineTagsList.get(i).name().equals("@more")) { 209 more = i; 210 } 211 } 212 if (more >= 0) { 213 for (int i=0; i<more; i++) { 214 mBriefTagsList.add(mInlineTagsList.get(i)); 215 } 216 } else { 217 for (int i=0; i<N; i++) { 218 TagInfo t = mInlineTagsList.get(i); 219 if (t.name().equals("Text")) { 220 Matcher m = FIRST_SENTENCE.matcher(t.text()); 221 if (m.matches()) { 222 String text = m.group(1); 223 TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position()); 224 mBriefTagsList.add(firstSentenceTag); 225 break; 226 } 227 } 228 mBriefTagsList.add(t); 229 230 } 231 } 232 } 233 tags()234 public TagInfo[] tags() 235 { 236 init(); 237 return mInlineTags; 238 } 239 tags(String name)240 public TagInfo[] tags(String name) 241 { 242 init(); 243 ArrayList<TagInfo> results = new ArrayList<TagInfo>(); 244 int N = mInlineTagsList.size(); 245 for (int i=0; i<N; i++) { 246 TagInfo t = mInlineTagsList.get(i); 247 if (t.name().equals(name)) { 248 results.add(t); 249 } 250 } 251 return results.toArray(new TagInfo[results.size()]); 252 } 253 paramTags()254 public ParamTagInfo[] paramTags() 255 { 256 init(); 257 return mParamTags; 258 } 259 seeTags()260 public SeeTagInfo[] seeTags() 261 { 262 init(); 263 return mSeeTags; 264 } 265 throwsTags()266 public ThrowsTagInfo[] throwsTags() 267 { 268 init(); 269 return mThrowsTags; 270 } 271 returnTags()272 public TagInfo[] returnTags() 273 { 274 init(); 275 return mReturnTags; 276 } 277 deprecatedTags()278 public TagInfo[] deprecatedTags() 279 { 280 init(); 281 return mDeprecatedTags; 282 } 283 undeprecateTags()284 public TagInfo[] undeprecateTags() 285 { 286 init(); 287 return mUndeprecateTags; 288 } 289 attrTags()290 public AttrTagInfo[] attrTags() 291 { 292 init(); 293 return mAttrTags; 294 } 295 briefTags()296 public TagInfo[] briefTags() 297 { 298 init(); 299 return mBriefTags; 300 } 301 isHidden()302 public boolean isHidden() 303 { 304 if (mHidden >= 0) { 305 return mHidden != 0; 306 } else { 307 if (DroidDoc.checkLevel(DroidDoc.SHOW_HIDDEN)) { 308 mHidden = 0; 309 return false; 310 } 311 boolean b = mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0; 312 mHidden = b ? 1 : 0; 313 return b; 314 } 315 } 316 isDocOnly()317 public boolean isDocOnly() { 318 if (mDocOnly >= 0) { 319 return mDocOnly != 0; 320 } else { 321 boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0); 322 mDocOnly = b ? 1 : 0; 323 return b; 324 } 325 } 326 init()327 private void init() 328 { 329 if (!mInitialized) { 330 initImpl(); 331 } 332 } 333 initImpl()334 private void initImpl() 335 { 336 isHidden(); 337 isDocOnly(); 338 339 // Don't bother parsing text if we aren't generating documentation. 340 if (DroidDoc.parseComments()) { 341 parseRegex(mText); 342 parseBriefTags(); 343 } else { 344 // Forces methods to be recognized by findOverriddenMethods in MethodInfo. 345 mInlineTagsList.add(new TextTagInfo("Text", "Text", mText, 346 SourcePositionInfo.add(mPosition, mText, 0))); 347 } 348 349 mText = null; 350 mInitialized = true; 351 352 mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]); 353 mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]); 354 mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]); 355 mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]); 356 mReturnTags = ParsedTagInfo.joinTags(mReturnTagsList.toArray( 357 new ParsedTagInfo[mReturnTagsList.size()])); 358 mDeprecatedTags = ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray( 359 new ParsedTagInfo[mDeprecatedTagsList.size()])); 360 mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]); 361 mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]); 362 mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]); 363 364 mParamTagsList = null; 365 mSeeTagsList = null; 366 mThrowsTagsList = null; 367 mReturnTagsList = null; 368 mDeprecatedTagsList = null; 369 mUndeprecateTagsList = null; 370 mAttrTagsList = null; 371 mBriefTagsList = null; 372 } 373 374 boolean mInitialized; 375 int mHidden = -1; 376 int mDocOnly = -1; 377 String mText; 378 ContainerInfo mBase; 379 SourcePositionInfo mPosition; 380 int mLine = 1; 381 382 TagInfo[] mInlineTags; 383 TagInfo[] mTags; 384 ParamTagInfo[] mParamTags; 385 SeeTagInfo[] mSeeTags; 386 ThrowsTagInfo[] mThrowsTags; 387 TagInfo[] mBriefTags; 388 TagInfo[] mReturnTags; 389 TagInfo[] mDeprecatedTags; 390 TagInfo[] mUndeprecateTags; 391 AttrTagInfo[] mAttrTags; 392 393 ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>(); 394 ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>(); 395 ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>(); 396 ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>(); 397 ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>(); 398 ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>(); 399 ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>(); 400 ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>(); 401 ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>(); 402 ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>(); 403 404 405 } 406