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