1/* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.javadoc.main; 27 28import java.util.regex.Matcher; 29import java.util.regex.Pattern; 30import com.sun.javadoc.*; 31import com.sun.tools.javac.util.ListBuffer; 32 33/** 34 * Comment contains all information in comment part. 35 * It allows users to get first sentence of this comment, get 36 * comment for different tags... 37 * 38 * <p><b>This is NOT part of any supported API. 39 * If you write code that depends on this, you do so at your own risk. 40 * This code and its internal interfaces are subject to change or 41 * deletion without notice.</b> 42 * 43 * @author Kaiyang Liu (original) 44 * @author Robert Field (rewrite) 45 * @author Atul M Dambalkar 46 * @author Neal Gafter (rewrite) 47 */ 48@Deprecated 49class Comment { 50 51 /** 52 * sorted comments with different tags. 53 */ 54 private final ListBuffer<Tag> tagList = new ListBuffer<>(); 55 56 /** 57 * text minus any tags. 58 */ 59 private String text; 60 61 /** 62 * Doc environment 63 */ 64 private final DocEnv docenv; 65 66 /** 67 * constructor of Comment. 68 */ 69 Comment(final DocImpl holder, final String commentString) { 70 this.docenv = holder.env; 71 72 /** 73 * Separate the comment into the text part and zero to N tags. 74 * Simple state machine is in one of three states: 75 * <pre> 76 * IN_TEXT: parsing the comment text or tag text. 77 * TAG_NAME: parsing the name of a tag. 78 * TAG_GAP: skipping through the gap between the tag name and 79 * the tag text. 80 * </pre> 81 */ 82 @SuppressWarnings("fallthrough") 83 class CommentStringParser { 84 /** 85 * The entry point to the comment string parser 86 */ 87 void parseCommentStateMachine() { 88 final int IN_TEXT = 1; 89 final int TAG_GAP = 2; 90 final int TAG_NAME = 3; 91 int state = TAG_GAP; 92 boolean newLine = true; 93 String tagName = null; 94 int tagStart = 0; 95 int textStart = 0; 96 int lastNonWhite = -1; 97 int len = commentString.length(); 98 for (int inx = 0; inx < len; ++inx) { 99 char ch = commentString.charAt(inx); 100 boolean isWhite = Character.isWhitespace(ch); 101 switch (state) { 102 case TAG_NAME: 103 if (isWhite) { 104 tagName = commentString.substring(tagStart, inx); 105 state = TAG_GAP; 106 } 107 break; 108 case TAG_GAP: 109 if (isWhite) { 110 break; 111 } 112 textStart = inx; 113 state = IN_TEXT; 114 /* fall thru */ 115 case IN_TEXT: 116 if (newLine && ch == '@') { 117 parseCommentComponent(tagName, textStart, 118 lastNonWhite+1); 119 tagStart = inx; 120 state = TAG_NAME; 121 } 122 break; 123 } 124 if (ch == '\n') { 125 newLine = true; 126 } else if (!isWhite) { 127 lastNonWhite = inx; 128 newLine = false; 129 } 130 } 131 // Finish what's currently being processed 132 switch (state) { 133 case TAG_NAME: 134 tagName = commentString.substring(tagStart, len); 135 /* fall thru */ 136 case TAG_GAP: 137 textStart = len; 138 /* fall thru */ 139 case IN_TEXT: 140 parseCommentComponent(tagName, textStart, lastNonWhite+1); 141 break; 142 } 143 } 144 145 /** 146 * Save away the last parsed item. 147 */ 148 void parseCommentComponent(String tagName, 149 int from, int upto) { 150 String tx = upto <= from ? "" : commentString.substring(from, upto); 151 if (tagName == null) { 152 text = tx; 153 } else { 154 TagImpl tag; 155 switch (tagName) { 156 case "@exception": 157 case "@throws": 158 warnIfEmpty(tagName, tx); 159 tag = new ThrowsTagImpl(holder, tagName, tx); 160 break; 161 case "@param": 162 warnIfEmpty(tagName, tx); 163 tag = new ParamTagImpl(holder, tagName, tx); 164 break; 165 case "@see": 166 warnIfEmpty(tagName, tx); 167 tag = new SeeTagImpl(holder, tagName, tx); 168 break; 169 case "@serialField": 170 warnIfEmpty(tagName, tx); 171 tag = new SerialFieldTagImpl(holder, tagName, tx); 172 break; 173 case "@return": 174 warnIfEmpty(tagName, tx); 175 tag = new TagImpl(holder, tagName, tx); 176 break; 177 case "@author": 178 warnIfEmpty(tagName, tx); 179 tag = new TagImpl(holder, tagName, tx); 180 break; 181 case "@version": 182 warnIfEmpty(tagName, tx); 183 tag = new TagImpl(holder, tagName, tx); 184 break; 185 default: 186 tag = new TagImpl(holder, tagName, tx); 187 break; 188 } 189 tagList.append(tag); 190 } 191 } 192 193 void warnIfEmpty(String tagName, String tx) { 194 if (tx.length() == 0) { 195 docenv.warning(holder, "tag.tag_has_no_arguments", tagName); 196 } 197 } 198 199 } 200 201 new CommentStringParser().parseCommentStateMachine(); 202 } 203 204 /** 205 * Return the text of the comment. 206 */ 207 String commentText() { 208 return text; 209 } 210 211 /** 212 * Return all tags in this comment. 213 */ 214 Tag[] tags() { 215 return tagList.toArray(new Tag[tagList.length()]); 216 } 217 218 /** 219 * Return tags of the specified kind in this comment. 220 */ 221 Tag[] tags(String tagname) { 222 ListBuffer<Tag> found = new ListBuffer<>(); 223 String target = tagname; 224 if (target.charAt(0) != '@') { 225 target = "@" + target; 226 } 227 for (Tag tag : tagList) { 228 if (tag.kind().equals(target)) { 229 found.append(tag); 230 } 231 } 232 return found.toArray(new Tag[found.length()]); 233 } 234 235 /** 236 * Return throws tags in this comment. 237 */ 238 ThrowsTag[] throwsTags() { 239 ListBuffer<ThrowsTag> found = new ListBuffer<>(); 240 for (Tag next : tagList) { 241 if (next instanceof ThrowsTag) { 242 found.append((ThrowsTag)next); 243 } 244 } 245 return found.toArray(new ThrowsTag[found.length()]); 246 } 247 248 /** 249 * Return param tags (excluding type param tags) in this comment. 250 */ 251 ParamTag[] paramTags() { 252 return paramTags(false); 253 } 254 255 /** 256 * Return type param tags in this comment. 257 */ 258 ParamTag[] typeParamTags() { 259 return paramTags(true); 260 } 261 262 /** 263 * Return param tags in this comment. If typeParams is true 264 * include only type param tags, otherwise include only ordinary 265 * param tags. 266 */ 267 private ParamTag[] paramTags(boolean typeParams) { 268 ListBuffer<ParamTag> found = new ListBuffer<>(); 269 for (Tag next : tagList) { 270 if (next instanceof ParamTag) { 271 ParamTag p = (ParamTag)next; 272 if (typeParams == p.isTypeParameter()) { 273 found.append(p); 274 } 275 } 276 } 277 return found.toArray(new ParamTag[found.length()]); 278 } 279 280 /** 281 * Return see also tags in this comment. 282 */ 283 SeeTag[] seeTags() { 284 ListBuffer<SeeTag> found = new ListBuffer<>(); 285 for (Tag next : tagList) { 286 if (next instanceof SeeTag) { 287 found.append((SeeTag)next); 288 } 289 } 290 return found.toArray(new SeeTag[found.length()]); 291 } 292 293 /** 294 * Return serialField tags in this comment. 295 */ 296 SerialFieldTag[] serialFieldTags() { 297 ListBuffer<SerialFieldTag> found = new ListBuffer<>(); 298 for (Tag next : tagList) { 299 if (next instanceof SerialFieldTag) { 300 found.append((SerialFieldTag)next); 301 } 302 } 303 return found.toArray(new SerialFieldTag[found.length()]); 304 } 305 306 /** 307 * Return array of tags with text and inline See Tags for a Doc comment. 308 */ 309 static Tag[] getInlineTags(DocImpl holder, String inlinetext) { 310 ListBuffer<Tag> taglist = new ListBuffer<>(); 311 int delimend = 0, textstart = 0, len = inlinetext.length(); 312 boolean inPre = false; 313 DocEnv docenv = holder.env; 314 315 if (len == 0) { 316 return taglist.toArray(new Tag[taglist.length()]); 317 } 318 while (true) { 319 int linkstart; 320 if ((linkstart = inlineTagFound(holder, inlinetext, 321 textstart)) == -1) { 322 taglist.append(new TagImpl(holder, "Text", 323 inlinetext.substring(textstart))); 324 break; 325 } else { 326 inPre = scanForPre(inlinetext, textstart, linkstart, inPre); 327 int seetextstart = linkstart; 328 for (int i = linkstart; i < inlinetext.length(); i++) { 329 char c = inlinetext.charAt(i); 330 if (Character.isWhitespace(c) || 331 c == '}') { 332 seetextstart = i; 333 break; 334 } 335 } 336 String linkName = inlinetext.substring(linkstart+2, seetextstart); 337 if (!(inPre && (linkName.equals("code") || linkName.equals("literal")))) { 338 //Move past the white space after the inline tag name. 339 while (Character.isWhitespace(inlinetext. 340 charAt(seetextstart))) { 341 if (inlinetext.length() <= seetextstart) { 342 taglist.append(new TagImpl(holder, "Text", 343 inlinetext.substring(textstart, seetextstart))); 344 docenv.warning(holder, 345 "tag.Improper_Use_Of_Link_Tag", 346 inlinetext); 347 return taglist.toArray(new Tag[taglist.length()]); 348 } else { 349 seetextstart++; 350 } 351 } 352 } 353 taglist.append(new TagImpl(holder, "Text", 354 inlinetext.substring(textstart, linkstart))); 355 textstart = seetextstart; // this text is actually seetag 356 if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) { 357 //Missing closing '}' character. 358 // store the text as it is with the {@link. 359 taglist.append(new TagImpl(holder, "Text", 360 inlinetext.substring(textstart))); 361 docenv.warning(holder, 362 "tag.End_delimiter_missing_for_possible_SeeTag", 363 inlinetext); 364 return taglist.toArray(new Tag[taglist.length()]); 365 } else { 366 //Found closing '}' character. 367 if (linkName.equals("see") 368 || linkName.equals("link") 369 || linkName.equals("linkplain")) { 370 taglist.append( new SeeTagImpl(holder, "@" + linkName, 371 inlinetext.substring(textstart, delimend))); 372 } else { 373 taglist.append( new TagImpl(holder, "@" + linkName, 374 inlinetext.substring(textstart, delimend))); 375 } 376 textstart = delimend + 1; 377 } 378 } 379 if (textstart == inlinetext.length()) { 380 break; 381 } 382 } 383 return taglist.toArray(new Tag[taglist.length()]); 384 } 385 386 /** regex for case-insensitive match for {@literal <pre> } and {@literal </pre> }. */ 387 private static final Pattern prePat = Pattern.compile("(?i)<(/?)pre>"); 388 389 private static boolean scanForPre(String inlinetext, int start, int end, boolean inPre) { 390 Matcher m = prePat.matcher(inlinetext).region(start, end); 391 while (m.find()) { 392 inPre = m.group(1).isEmpty(); 393 } 394 return inPre; 395 } 396 397 /** 398 * Recursively find the index of the closing '}' character for an inline tag 399 * and return it. If it can't be found, return -1. 400 * @param inlineText the text to search in. 401 * @param searchStart the index of the place to start searching at. 402 * @return the index of the closing '}' character for an inline tag. 403 * If it can't be found, return -1. 404 */ 405 private static int findInlineTagDelim(String inlineText, int searchStart) { 406 int delimEnd, nestedOpenBrace; 407 if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) { 408 return -1; 409 } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) && 410 nestedOpenBrace < delimEnd){ 411 //Found a nested open brace. 412 int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1); 413 return (nestedCloseBrace != -1) ? 414 findInlineTagDelim(inlineText, nestedCloseBrace + 1) : 415 -1; 416 } else { 417 return delimEnd; 418 } 419 } 420 421 /** 422 * Recursively search for the characters '{', '@', followed by 423 * name of inline tag and white space, 424 * if found 425 * return the index of the text following the white space. 426 * else 427 * return -1. 428 */ 429 private static int inlineTagFound(DocImpl holder, String inlinetext, int start) { 430 DocEnv docenv = holder.env; 431 int linkstart = inlinetext.indexOf("{@", start); 432 if (start == inlinetext.length() || linkstart == -1) { 433 return -1; 434 } else if (inlinetext.indexOf('}', linkstart) == -1) { 435 //Missing '}'. 436 docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag", 437 inlinetext.substring(linkstart, inlinetext.length())); 438 return -1; 439 } else { 440 return linkstart; 441 } 442 } 443 444 445 /** 446 * Return array of tags for the locale specific first sentence in the text. 447 */ 448 static Tag[] firstSentenceTags(DocImpl holder, String text) { 449 DocLocale doclocale = holder.env.doclocale; 450 return getInlineTags(holder, 451 doclocale.localeSpecificFirstSentence(holder, text)); 452 } 453 454 /** 455 * Return text for this Doc comment. 456 */ 457 @Override 458 public String toString() { 459 return text; 460 } 461} 462