1 /*
   2  * Copyright (c) 2012, 2019, 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.javac.parser;
  27 
  28 import java.text.BreakIterator;
  29 import java.util.HashMap;
  30 import java.util.Map;
  31 
  32 import com.sun.source.doctree.AttributeTree.ValueKind;
  33 import com.sun.source.doctree.DocTree;
  34 import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
  35 import com.sun.tools.javac.parser.Tokens.Comment;
  36 import com.sun.tools.javac.parser.Tokens.TokenKind;
  37 import com.sun.tools.javac.tree.DCTree;
  38 import com.sun.tools.javac.tree.DCTree.DCAttribute;
  39 import com.sun.tools.javac.tree.DCTree.DCDocComment;
  40 import com.sun.tools.javac.tree.DCTree.DCEndElement;
  41 import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
  42 import com.sun.tools.javac.tree.DCTree.DCErroneous;
  43 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
  44 import com.sun.tools.javac.tree.DCTree.DCReference;
  45 import com.sun.tools.javac.tree.DCTree.DCStartElement;
  46 import com.sun.tools.javac.tree.DCTree.DCText;
  47 import com.sun.tools.javac.tree.DocTreeMaker;
  48 import com.sun.tools.javac.tree.JCTree;
  49 import com.sun.tools.javac.util.DiagnosticSource;
  50 import com.sun.tools.javac.util.List;
  51 import com.sun.tools.javac.util.ListBuffer;
  52 import com.sun.tools.javac.util.Log;
  53 import com.sun.tools.javac.util.Name;
  54 import com.sun.tools.javac.util.Names;
  55 import com.sun.tools.javac.util.Position;
  56 import com.sun.tools.javac.util.StringUtils;
  57 
  58 import static com.sun.tools.javac.util.LayoutCharacters.*;
  59 
  60 /**
  61  *
  62  *  <p><b>This is NOT part of any supported API.
  63  *  If you write code that depends on this, you do so at your own risk.
  64  *  This code and its internal interfaces are subject to change or
  65  *  deletion without notice.</b>
  66  */
  67 public class DocCommentParser {
  68     static class ParseException extends Exception {
  69         private static final long serialVersionUID = 0;
  70         ParseException(String key) {
  71             super(key);
  72         }
  73     }
  74 
  75     private enum Phase {PREAMBLE, BODY, POSTAMBLE};
  76 
  77     final ParserFactory fac;
  78     final DiagnosticSource diagSource;
  79     final Comment comment;
  80     final DocTreeMaker m;
  81     final Names names;
  82     final boolean isFileContent;
  83 
  84     BreakIterator sentenceBreaker;
  85 
  86     /** The input buffer, index of most recent character read,
  87      *  index of one past last character in buffer.
  88      */
  89     protected char[] buf;
  90     protected int bp;
  91     protected int buflen;
  92 
  93     /** The current character.
  94      */
  95     protected char ch;
  96 
  97     int textStart = -1;
  98     int lastNonWhite = -1;
  99     boolean newline = true;
 100 
 101     Map<Name, TagParser> tagParsers;
 102 
 103     public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource,
 104                             Comment comment, boolean isFileContent) {
 105         this.fac = fac;
 106         this.diagSource = diagSource;
 107         this.comment = comment;
 108         names = fac.names;
 109         this.isFileContent = isFileContent;
 110         m = fac.docTreeMaker;
 111         initTagParsers();
 112     }
 113 
 114     public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
 115         this(fac, diagSource, comment, false);
 116     }
 117 
 118     public DocCommentParser(ParserFactory fac) {
 119         this(fac, null, null, false);
 120     }
 121 
 122     public DCDocComment parse() {
 123         String c = comment.getText();
 124         buf = new char[c.length() + 1];
 125         c.getChars(0, c.length(), buf, 0);
 126         buf[buf.length - 1] = EOI;
 127         buflen = buf.length - 1;
 128         bp = -1;
 129         nextChar();
 130 
 131         List<DCTree> preamble = isFileContent ? blockContent(Phase.PREAMBLE) : List.nil();
 132         List<DCTree> body = blockContent(Phase.BODY);
 133         List<DCTree> tags = blockTags();
 134         List<DCTree> postamble = isFileContent ? blockContent(Phase.POSTAMBLE) : List.nil();
 135 
 136         int pos = Position.NOPOS;
 137         if (!preamble.isEmpty())
 138             pos = preamble.head.pos;
 139         else if (!body.isEmpty())
 140             pos = body.head.pos;
 141         else if (!tags.isEmpty())
 142             pos = tags.head.pos;
 143         else if (!postamble.isEmpty())
 144             pos = postamble.head.pos;
 145 
 146         DCDocComment dc = m.at(pos).newDocCommentTree(comment, body, tags, preamble, postamble);
 147         return dc;
 148     }
 149 
 150     void nextChar() {
 151         ch = buf[bp < buflen ? ++bp : buflen];
 152         switch (ch) {
 153             case '\f': case '\n': case '\r':
 154                 newline = true;
 155         }
 156     }
 157 
 158     protected List<DCTree> blockContent() {
 159         return blockContent(Phase.BODY);
 160     }
 161 
 162     /**
 163      * Read block content, consisting of text, html and inline tags.
 164      * Terminated by the end of input, or the beginning of the next block tag:
 165      * i.e. @ as the first non-whitespace character on a line.
 166      */
 167     @SuppressWarnings("fallthrough")
 168     protected List<DCTree> blockContent(Phase phase) {
 169         ListBuffer<DCTree> trees = new ListBuffer<>();
 170         textStart = -1;
 171 
 172         loop:
 173         while (bp < buflen) {
 174             switch (ch) {
 175                 case '\n': case '\r': case '\f':
 176                     newline = true;
 177                     // fallthrough
 178 
 179                 case ' ': case '\t':
 180                     nextChar();
 181                     break;
 182 
 183                 case '&':
 184                     entity(trees);
 185                     break;
 186 
 187                 case '<':
 188                     newline = false;
 189                     if (isFileContent) {
 190                         switch (phase) {
 191                             case PREAMBLE:
 192                                 if (isEndPreamble()) {
 193                                     trees.add(html());
 194                                     if (textStart == -1) {
 195                                         textStart = bp;
 196                                         lastNonWhite = -1;
 197                                     }
 198                                     // mark this as the start, for processing purposes
 199                                     newline = true;
 200                                     break loop;
 201                                 }
 202                                 break;
 203                             case BODY:
 204                                 if (isEndBody()) {
 205                                     addPendingText(trees, lastNonWhite);
 206                                     break loop;
 207                                 }
 208                                 break;
 209                             default:
 210                                 // fallthrough
 211                         }
 212                     }
 213                     addPendingText(trees, bp - 1);
 214                     trees.add(html());
 215 
 216                     if (phase == Phase.PREAMBLE || phase == Phase.POSTAMBLE) {
 217                         break; // Ignore newlines after html tags, in the meta content
 218                     }
 219                     if (textStart == -1) {
 220                         textStart = bp;
 221                         lastNonWhite = -1;
 222                     }
 223                     break;
 224 
 225                 case '>':
 226                     newline = false;
 227                     addPendingText(trees, bp - 1);
 228                     trees.add(m.at(bp).newErroneousTree(newString(bp, bp + 1), diagSource, "dc.bad.gt"));
 229                     nextChar();
 230                     if (textStart == -1) {
 231                         textStart = bp;
 232                         lastNonWhite = -1;
 233                     }
 234                     break;
 235 
 236                 case '{':
 237                     inlineTag(trees);
 238                     break;
 239 
 240                 case '@':
 241                     if (newline) {
 242                         addPendingText(trees, lastNonWhite);
 243                         break loop;
 244                     }
 245                     // fallthrough
 246 
 247                 default:
 248                     newline = false;
 249                     if (textStart == -1)
 250                         textStart = bp;
 251                     lastNonWhite = bp;
 252                     nextChar();
 253             }
 254         }
 255 
 256         if (lastNonWhite != -1)
 257             addPendingText(trees, lastNonWhite);
 258 
 259         return trees.toList();
 260     }
 261 
 262     /**
 263      * Read a series of block tags, including their content.
 264      * Standard tags parse their content appropriately.
 265      * Non-standard tags are represented by {@link UnknownBlockTag}.
 266      */
 267     protected List<DCTree> blockTags() {
 268         ListBuffer<DCTree> tags = new ListBuffer<>();
 269         while (ch == '@')
 270             tags.add(blockTag());
 271         return tags.toList();
 272     }
 273 
 274     /**
 275      * Read a single block tag, including its content.
 276      * Standard tags parse their content appropriately.
 277      * Non-standard tags are represented by {@link UnknownBlockTag}.
 278      */
 279     protected DCTree blockTag() {
 280         int p = bp;
 281         try {
 282             nextChar();
 283             if (isIdentifierStart(ch)) {
 284                 Name name = readTagName();
 285                 TagParser tp = tagParsers.get(name);
 286                 if (tp == null) {
 287                     List<DCTree> content = blockContent();
 288                     return m.at(p).newUnknownBlockTagTree(name, content);
 289                 } else {
 290                     switch (tp.getKind()) {
 291                         case BLOCK:
 292                             return tp.parse(p);
 293                         case INLINE:
 294                             return erroneous("dc.bad.inline.tag", p);
 295                     }
 296                 }
 297             }
 298             blockContent();
 299 
 300             return erroneous("dc.no.tag.name", p);
 301         } catch (ParseException e) {
 302             blockContent();
 303             return erroneous(e.getMessage(), p);
 304         }
 305     }
 306 
 307     protected void inlineTag(ListBuffer<DCTree> list) {
 308         newline = false;
 309         nextChar();
 310         if (ch == '@') {
 311             addPendingText(list, bp - 2);
 312             list.add(inlineTag());
 313             textStart = bp;
 314             lastNonWhite = -1;
 315         } else {
 316             if (textStart == -1)
 317                 textStart = bp - 1;
 318             lastNonWhite = bp;
 319         }
 320     }
 321 
 322     /**
 323      * Read a single inline tag, including its content.
 324      * Standard tags parse their content appropriately.
 325      * Non-standard tags are represented by {@link UnknownBlockTag}.
 326      * Malformed tags may be returned as {@link Erroneous}.
 327      */
 328     protected DCTree inlineTag() {
 329         int p = bp - 1;
 330         try {
 331             nextChar();
 332             if (isIdentifierStart(ch)) {
 333                 Name name = readTagName();
 334                 TagParser tp = tagParsers.get(name);
 335 
 336                 if (tp == null) {
 337                     skipWhitespace();
 338                     DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
 339                     if (text != null) {
 340                         nextChar();
 341                         return m.at(p).newUnknownInlineTagTree(name, List.of(text)).setEndPos(bp);
 342                     }
 343                 } else {
 344                     if (!tp.retainWhiteSpace) {
 345                         skipWhitespace();
 346                     }
 347                     if (tp.getKind() == TagParser.Kind.INLINE) {
 348                         DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
 349                         if (tree != null) {
 350                             return tree.setEndPos(bp);
 351                         }
 352                     } else { // handle block tags (ex: @see) in inline content
 353                         inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
 354                         nextChar();
 355                     }
 356                 }
 357             }
 358             return erroneous("dc.no.tag.name", p);
 359         } catch (ParseException e) {
 360             return erroneous(e.getMessage(), p);
 361         }
 362     }
 363 
 364     private static enum WhitespaceRetentionPolicy {
 365         RETAIN_ALL,
 366         REMOVE_FIRST_SPACE,
 367         REMOVE_ALL
 368     }
 369 
 370     /**
 371      * Read plain text content of an inline tag.
 372      * Matching pairs of { } are skipped; the text is terminated by the first
 373      * unmatched }. It is an error if the beginning of the next tag is detected.
 374      */
 375     private DCTree inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
 376         switch (whitespacePolicy) {
 377             case REMOVE_ALL:
 378                 skipWhitespace();
 379                 break;
 380             case REMOVE_FIRST_SPACE:
 381                 if (ch == ' ')
 382                     nextChar();
 383                 break;
 384             case RETAIN_ALL:
 385             default:
 386                 // do nothing
 387                 break;
 388 
 389         }
 390         int pos = bp;
 391         int depth = 1;
 392 
 393         loop:
 394         while (bp < buflen) {
 395             switch (ch) {
 396                 case '\n': case '\r': case '\f':
 397                     newline = true;
 398                     break;
 399 
 400                 case ' ': case '\t':
 401                     break;
 402 
 403                 case '{':
 404                     newline = false;
 405                     lastNonWhite = bp;
 406                     depth++;
 407                     break;
 408 
 409                 case '}':
 410                     if (--depth == 0) {
 411                         return m.at(pos).newTextTree(newString(pos, bp));
 412                     }
 413                     newline = false;
 414                     lastNonWhite = bp;
 415                     break;
 416 
 417                 case '@':
 418                     if (newline)
 419                         break loop;
 420                     newline = false;
 421                     lastNonWhite = bp;
 422                     break;
 423 
 424                 default:
 425                     newline = false;
 426                     lastNonWhite = bp;
 427                     break;
 428             }
 429             nextChar();
 430         }
 431         throw new ParseException("dc.unterminated.inline.tag");
 432     }
 433 
 434     /**
 435      * Read Java class name, possibly followed by member
 436      * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
 437      * unmatched }. It is an error if the beginning of the next tag is detected.
 438      */
 439     // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
 440     // TODO: improve quality of parse to forbid bad constructions.
 441     // TODO: update to use ReferenceParser
 442     @SuppressWarnings("fallthrough")
 443     protected DCReference reference(boolean allowMember) throws ParseException {
 444         int pos = bp;
 445         int depth = 0;
 446 
 447         // scan to find the end of the signature, by looking for the first
 448         // whitespace not enclosed in () or <>, or the end of the tag
 449         loop:
 450         while (bp < buflen) {
 451             switch (ch) {
 452                 case '\n': case '\r': case '\f':
 453                     newline = true;
 454                     // fallthrough
 455 
 456                 case ' ': case '\t':
 457                     if (depth == 0)
 458                         break loop;
 459                     break;
 460 
 461                 case '(':
 462                 case '<':
 463                     newline = false;
 464                     depth++;
 465                     break;
 466 
 467                 case ')':
 468                 case '>':
 469                     newline = false;
 470                     --depth;
 471                     break;
 472 
 473                 case '}':
 474                     if (bp == pos)
 475                         return null;
 476                     newline = false;
 477                     break loop;
 478 
 479                 case '@':
 480                     if (newline)
 481                         break loop;
 482                     // fallthrough
 483 
 484                 default:
 485                     newline = false;
 486 
 487             }
 488             nextChar();
 489         }
 490 
 491         if (depth != 0)
 492             throw new ParseException("dc.unterminated.signature");
 493 
 494         String sig = newString(pos, bp);
 495 
 496         // Break sig apart into qualifiedExpr member paramTypes.
 497         JCTree qualExpr;
 498         Name member;
 499         List<JCTree> paramTypes;
 500 
 501         Log.DeferredDiagnosticHandler deferredDiagnosticHandler
 502                 = new Log.DeferredDiagnosticHandler(fac.log);
 503 
 504         try {
 505             int hash = sig.indexOf("#");
 506             int lparen = sig.indexOf("(", hash + 1);
 507             if (hash == -1) {
 508                 if (lparen == -1) {
 509                     qualExpr = parseType(sig);
 510                     member = null;
 511                 } else {
 512                     qualExpr = null;
 513                     member = parseMember(sig.substring(0, lparen));
 514                 }
 515             } else {
 516                 qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
 517                 if (lparen == -1)
 518                     member = parseMember(sig.substring(hash + 1));
 519                 else
 520                     member = parseMember(sig.substring(hash + 1, lparen));
 521             }
 522 
 523             if (lparen < 0) {
 524                 paramTypes = null;
 525             } else {
 526                 int rparen = sig.indexOf(")", lparen);
 527                 if (rparen != sig.length() - 1)
 528                     throw new ParseException("dc.ref.bad.parens");
 529                 paramTypes = parseParams(sig.substring(lparen + 1, rparen));
 530             }
 531 
 532             if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
 533                 throw new ParseException("dc.ref.syntax.error");
 534 
 535         } finally {
 536             fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
 537         }
 538 
 539         return m.at(pos).newReferenceTree(sig, qualExpr, member, paramTypes).setEndPos(bp);
 540     }
 541 
 542     JCTree parseType(String s) throws ParseException {
 543         JavacParser p = fac.newParser(s, false, false, false);
 544         JCTree tree = p.parseType();
 545         if (p.token().kind != TokenKind.EOF)
 546             throw new ParseException("dc.ref.unexpected.input");
 547         return tree;
 548     }
 549 
 550     Name parseMember(String s) throws ParseException {
 551         JavacParser p = fac.newParser(s, false, false, false);
 552         Name name = p.ident();
 553         if (p.token().kind != TokenKind.EOF)
 554             throw new ParseException("dc.ref.unexpected.input");
 555         return name;
 556     }
 557 
 558     List<JCTree> parseParams(String s) throws ParseException {
 559         if (s.trim().isEmpty())
 560             return List.nil();
 561 
 562         JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
 563         ListBuffer<JCTree> paramTypes = new ListBuffer<>();
 564         paramTypes.add(p.parseType());
 565 
 566         if (p.token().kind == TokenKind.IDENTIFIER)
 567             p.nextToken();
 568 
 569         while (p.token().kind == TokenKind.COMMA) {
 570             p.nextToken();
 571             paramTypes.add(p.parseType());
 572 
 573             if (p.token().kind == TokenKind.IDENTIFIER)
 574                 p.nextToken();
 575         }
 576 
 577         if (p.token().kind != TokenKind.EOF)
 578             throw new ParseException("dc.ref.unexpected.input");
 579 
 580         return paramTypes.toList();
 581     }
 582 
 583     /**
 584      * Read Java identifier
 585      * Matching pairs of { } are skipped; the text is terminated by the first
 586      * unmatched }. It is an error if the beginning of the next tag is detected.
 587      */
 588     @SuppressWarnings("fallthrough")
 589     protected DCIdentifier identifier() throws ParseException {
 590         skipWhitespace();
 591         int pos = bp;
 592 
 593         if (isJavaIdentifierStart(ch)) {
 594             Name name = readJavaIdentifier();
 595             return m.at(pos).newIdentifierTree(name);
 596         }
 597 
 598         throw new ParseException("dc.identifier.expected");
 599     }
 600 
 601     /**
 602      * Read a quoted string.
 603      * It is an error if the beginning of the next tag is detected.
 604      */
 605     @SuppressWarnings("fallthrough")
 606     protected DCText quotedString() {
 607         int pos = bp;
 608         nextChar();
 609 
 610         loop:
 611         while (bp < buflen) {
 612             switch (ch) {
 613                 case '\n': case '\r': case '\f':
 614                     newline = true;
 615                     break;
 616 
 617                 case ' ': case '\t':
 618                     break;
 619 
 620                 case '"':
 621                     nextChar();
 622                     // trim trailing white-space?
 623                     return m.at(pos).newTextTree(newString(pos, bp));
 624 
 625                 case '@':
 626                     if (newline)
 627                         break loop;
 628 
 629             }
 630             nextChar();
 631         }
 632         return null;
 633     }
 634 
 635     /**
 636      * Read a term ie. one word.
 637      * It is an error if the beginning of the next tag is detected.
 638      */
 639     @SuppressWarnings("fallthrough")
 640     protected DCText inlineWord() {
 641         int pos = bp;
 642         int depth = 0;
 643         loop:
 644         while (bp < buflen) {
 645             switch (ch) {
 646                 case '\n':
 647                     newline = true;
 648                     // fallthrough
 649 
 650                 case '\r': case '\f': case ' ': case '\t':
 651                     return m.at(pos).newTextTree(newString(pos, bp));
 652 
 653                 case '@':
 654                     if (newline)
 655                         break loop;
 656 
 657                 case '{':
 658                     depth++;
 659                     break;
 660 
 661                 case '}':
 662                     if (depth == 0 || --depth == 0)
 663                         return m.at(pos).newTextTree(newString(pos, bp));
 664                     break;
 665             }
 666             newline = false;
 667             nextChar();
 668         }
 669         return null;
 670     }
 671 
 672     /**
 673      * Read general text content of an inline tag, including HTML entities and elements.
 674      * Matching pairs of { } are skipped; the text is terminated by the first
 675      * unmatched }. It is an error if the beginning of the next tag is detected.
 676      */
 677     @SuppressWarnings("fallthrough")
 678     private List<DCTree> inlineContent() {
 679         ListBuffer<DCTree> trees = new ListBuffer<>();
 680 
 681         skipWhitespace();
 682         int pos = bp;
 683         int depth = 1;
 684         textStart = -1;
 685 
 686         loop:
 687         while (bp < buflen) {
 688 
 689             switch (ch) {
 690                 case '\n': case '\r': case '\f':
 691                     newline = true;
 692                     // fall through
 693 
 694                 case ' ': case '\t':
 695                     nextChar();
 696                     break;
 697 
 698                 case '&':
 699                     entity(trees);
 700                     break;
 701 
 702                 case '<':
 703                     newline = false;
 704                     addPendingText(trees, bp - 1);
 705                     trees.add(html());
 706                     break;
 707 
 708                 case '{':
 709                     if (textStart == -1)
 710                         textStart = bp;
 711                     newline = false;
 712                     depth++;
 713                     nextChar();
 714                     break;
 715 
 716                 case '}':
 717                     newline = false;
 718                     if (--depth == 0) {
 719                         addPendingText(trees, bp - 1);
 720                         nextChar();
 721                         return trees.toList();
 722                     }
 723                     nextChar();
 724                     break;
 725 
 726                 case '@':
 727                     if (newline)
 728                         break loop;
 729                     // fallthrough
 730 
 731                 default:
 732                     if (textStart == -1)
 733                         textStart = bp;
 734                     nextChar();
 735                     break;
 736             }
 737         }
 738 
 739         return List.of(erroneous("dc.unterminated.inline.tag", pos));
 740     }
 741 
 742     protected void entity(ListBuffer<DCTree> list) {
 743         newline = false;
 744         addPendingText(list, bp - 1);
 745         list.add(entity());
 746         if (textStart == -1) {
 747             textStart = bp;
 748             lastNonWhite = -1;
 749         }
 750     }
 751 
 752     /**
 753      * Read an HTML entity.
 754      * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
 755      */
 756     protected DCTree entity() {
 757         int p = bp;
 758         nextChar();
 759         Name name = null;
 760         if (ch == '#') {
 761             int namep = bp;
 762             nextChar();
 763             if (isDecimalDigit(ch)) {
 764                 nextChar();
 765                 while (isDecimalDigit(ch))
 766                     nextChar();
 767                 name = names.fromChars(buf, namep, bp - namep);
 768             } else if (ch == 'x' || ch == 'X') {
 769                 nextChar();
 770                 if (isHexDigit(ch)) {
 771                     nextChar();
 772                     while (isHexDigit(ch))
 773                         nextChar();
 774                     name = names.fromChars(buf, namep, bp - namep);
 775                 }
 776             }
 777         } else if (isIdentifierStart(ch)) {
 778             name = readIdentifier();
 779         }
 780 
 781         if (name == null)
 782             return erroneous("dc.bad.entity", p);
 783         else {
 784             if (ch != ';')
 785                 return erroneous("dc.missing.semicolon", p);
 786             nextChar();
 787             return m.at(p).newEntityTree(name);
 788         }
 789     }
 790 
 791     /**
 792      * Returns whether this is the end of the preamble of an HTML file.
 793      * The preamble ends with start of {@code body} element followed by
 794      * possible whitespace and the start of a {@code main} element.
 795      *
 796      * @return whether this is the end of the preamble
 797      */
 798     boolean isEndPreamble() {
 799         final int savedpos = bp;
 800         try {
 801             if (ch == '<')
 802                 nextChar();
 803 
 804             if (isIdentifierStart(ch)) {
 805                 String name = StringUtils.toLowerCase(readIdentifier().toString());
 806                 switch (name) {
 807                     case "body":
 808                         // Check if also followed by <main>
 809                         // 1. skip rest of <body>
 810                         while (ch != -1 && ch != '>') {
 811                             nextChar();
 812                         }
 813                         if (ch == '>') {
 814                             nextChar();
 815                         }
 816                         // 2. skip any whitespce
 817                         while (ch != -1 && Character.isWhitespace(ch)) {
 818                             nextChar();
 819                         }
 820                         // 3. check if looking at "<main..."
 821                         if (ch == '<') {
 822                             nextChar();
 823                             if (isIdentifierStart(ch)) {
 824                                 name = StringUtils.toLowerCase(readIdentifier().toString());
 825                                 if (name.equals("main")) {
 826                                     return false;
 827                                 }
 828                             }
 829                         }
 830                         // if <body> is _not_ followed by <main> then this is the
 831                         // end of the preamble
 832                         return true;
 833 
 834                     case "main":
 835                         // <main> is unconditionally the end of the preamble
 836                         return true;
 837                 }
 838             }
 839             return false;
 840         } finally {
 841             bp = savedpos;
 842             ch = buf[bp];
 843         }
 844     }
 845 
 846     /**
 847      * Returns whether this is the end of the main body of the content in a standalone
 848      * HTML file.
 849      * The content ends with the closing tag for a {@code main} or {@code body} element.
 850      *
 851      * @return whether this is the end of the main body of the content
 852      */
 853     boolean isEndBody() {
 854         final int savedpos = bp;
 855         try {
 856             if (ch == '<')
 857                 nextChar();
 858 
 859             if (ch == '/') {
 860                 nextChar();
 861                 if (isIdentifierStart(ch)) {
 862                     String name = StringUtils.toLowerCase(readIdentifier().toString());
 863                     switch (name) {
 864                         case "body":
 865                         case "main":
 866                             return true;
 867                     }
 868                 }
 869             }
 870 
 871             return false;
 872         } finally {
 873             bp = savedpos;
 874             ch = buf[bp];
 875         }
 876 
 877     }
 878 
 879     boolean peek(String s) {
 880         final int savedpos = bp;
 881         try {
 882             if (ch == '<')
 883                 nextChar();
 884 
 885             if (ch == '/') {
 886                 if (s.charAt(0) != ch) {
 887                     return false;
 888                 } else {
 889                     s = s.substring(1, s.length());
 890                     nextChar();
 891                 }
 892             }
 893 
 894             if (isIdentifierStart(ch)) {
 895                 Name name = readIdentifier();
 896                 return StringUtils.toLowerCase(name.toString()).equals(s);
 897             }
 898             return false;
 899         } finally {
 900             bp = savedpos;
 901             ch = buf[bp];
 902         }
 903     }
 904 
 905     /**
 906      * Read the start or end of an HTML tag, or an HTML comment
 907      * {@literal <identifier attrs> } or {@literal </identifier> }
 908      */
 909     private DCTree html() {
 910         int p = bp;
 911         nextChar();
 912         if (isIdentifierStart(ch)) {
 913             Name name = readIdentifier();
 914             List<DCTree> attrs = htmlAttrs();
 915             if (attrs != null) {
 916                 boolean selfClosing = false;
 917                 if (ch == '/') {
 918                     nextChar();
 919                     selfClosing = true;
 920                 }
 921                 if (ch == '>') {
 922                     nextChar();
 923                     DCTree dctree = m.at(p).newStartElementTree(name, attrs, selfClosing).setEndPos(bp);
 924                     return dctree;
 925                 }
 926             }
 927         } else if (ch == '/') {
 928             nextChar();
 929             if (isIdentifierStart(ch)) {
 930                 Name name = readIdentifier();
 931                 skipWhitespace();
 932                 if (ch == '>') {
 933                     nextChar();
 934                     return m.at(p).newEndElementTree(name);
 935                 }
 936             }
 937         } else if (ch == '!') {
 938             nextChar();
 939             if (ch == '-') {
 940                 nextChar();
 941                 if (ch == '-') {
 942                     nextChar();
 943                     while (bp < buflen) {
 944                         int dash = 0;
 945                         while (ch == '-') {
 946                             dash++;
 947                             nextChar();
 948                         }
 949                         // Strictly speaking, a comment should not contain "--"
 950                         // so dash > 2 is an error, dash == 2 implies ch == '>'
 951                         // See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
 952                         // for more details.
 953                         if (dash >= 2 && ch == '>') {
 954                             nextChar();
 955                             return m.at(p).newCommentTree(newString(p, bp));
 956                         }
 957 
 958                         nextChar();
 959                     }
 960                 }
 961             } else if (isIdentifierStart(ch) && peek("doctype")) {
 962                 readIdentifier();
 963                 nextChar();
 964                 skipWhitespace();
 965                 int d = bp;
 966                 while (bp < buflen) {
 967                     if (ch == '>') {
 968                         int mark = bp;
 969                         nextChar();
 970                         return m.at(d).newDocTypeTree(newString(d, mark));
 971                     }
 972                     nextChar();
 973                 }
 974             }
 975         }
 976 
 977         bp = p + 1;
 978         ch = buf[bp];
 979         return erroneous("dc.malformed.html", p);
 980     }
 981 
 982     /**
 983      * Read a series of HTML attributes, terminated by {@literal > }.
 984      * Each attribute is of the form {@literal identifier[=value] }.
 985      * "value" may be unquoted, single-quoted, or double-quoted.
 986      */
 987     protected List<DCTree> htmlAttrs() {
 988         ListBuffer<DCTree> attrs = new ListBuffer<>();
 989         skipWhitespace();
 990 
 991         loop:
 992         while (isIdentifierStart(ch)) {
 993             int namePos = bp;
 994             Name name = readAttributeName();
 995             skipWhitespace();
 996             List<DCTree> value = null;
 997             ValueKind vkind = ValueKind.EMPTY;
 998             if (ch == '=') {
 999                 ListBuffer<DCTree> v = new ListBuffer<>();
1000                 nextChar();
1001                 skipWhitespace();
1002                 if (ch == '\'' || ch == '"') {
1003                     vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE;
1004                     char quote = ch;
1005                     nextChar();
1006                     textStart = bp;
1007                     while (bp < buflen && ch != quote) {
1008                         if (newline && ch == '@') {
1009                             attrs.add(erroneous("dc.unterminated.string", namePos));
1010                             // No point trying to read more.
1011                             // In fact, all attrs get discarded by the caller
1012                             // and superseded by a malformed.html node because
1013                             // the html tag itself is not terminated correctly.
1014                             break loop;
1015                         }
1016                         attrValueChar(v);
1017                     }
1018                     addPendingText(v, bp - 1);
1019                     nextChar();
1020                 } else {
1021                     vkind = ValueKind.UNQUOTED;
1022                     textStart = bp;
1023                     while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
1024                         attrValueChar(v);
1025                     }
1026                     addPendingText(v, bp - 1);
1027                 }
1028                 skipWhitespace();
1029                 value = v.toList();
1030             }
1031             DCAttribute attr = m.at(namePos).newAttributeTree(name, vkind, value);
1032             attrs.add(attr);
1033         }
1034 
1035         return attrs.toList();
1036     }
1037 
1038     protected void attrValueChar(ListBuffer<DCTree> list) {
1039         switch (ch) {
1040             case '&':
1041                 entity(list);
1042                 break;
1043 
1044             case '{':
1045                 inlineTag(list);
1046                 break;
1047 
1048             default:
1049                 nextChar();
1050         }
1051     }
1052 
1053     protected void addPendingText(ListBuffer<DCTree> list, int textEnd) {
1054         if (textStart != -1) {
1055             if (textStart <= textEnd) {
1056                 list.add(m.at(textStart).newTextTree(newString(textStart, textEnd + 1)));
1057             }
1058             textStart = -1;
1059         }
1060     }
1061 
1062     protected DCErroneous erroneous(String code, int pos) {
1063         int i = bp - 1;
1064         loop:
1065         while (i > pos) {
1066             switch (buf[i]) {
1067                 case '\f': case '\n': case '\r':
1068                     newline = true;
1069                     break;
1070                 case '\t': case ' ':
1071                     break;
1072                 default:
1073                     break loop;
1074             }
1075             i--;
1076         }
1077         textStart = -1;
1078         return m.at(pos).newErroneousTree(newString(pos, i + 1), diagSource, code);
1079     }
1080 
1081     protected boolean isIdentifierStart(char ch) {
1082         return Character.isUnicodeIdentifierStart(ch);
1083     }
1084 
1085     protected Name readIdentifier() {
1086         int start = bp;
1087         nextChar();
1088         while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
1089             nextChar();
1090         return names.fromChars(buf, start, bp - start);
1091     }
1092 
1093     protected Name readAttributeName() {
1094         int start = bp;
1095         nextChar();
1096         while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
1097             nextChar();
1098         return names.fromChars(buf, start, bp - start);
1099     }
1100 
1101     protected Name readTagName() {
1102         int start = bp;
1103         nextChar();
1104         while (bp < buflen
1105                 && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
1106                 || ch == '-' || ch == ':')) {
1107             nextChar();
1108         }
1109         return names.fromChars(buf, start, bp - start);
1110     }
1111 
1112     protected boolean isJavaIdentifierStart(char ch) {
1113         return Character.isJavaIdentifierStart(ch);
1114     }
1115 
1116     protected Name readJavaIdentifier() {
1117         int start = bp;
1118         nextChar();
1119         while (bp < buflen && Character.isJavaIdentifierPart(ch))
1120             nextChar();
1121         return names.fromChars(buf, start, bp - start);
1122     }
1123 
1124     protected Name readSystemPropertyName() {
1125         int pos = bp;
1126         nextChar();
1127         while (bp < buflen && Character.isUnicodeIdentifierPart(ch) || ch == '.')
1128             nextChar();
1129         return names.fromChars(buf, pos, bp - pos);
1130     }
1131 
1132     protected boolean isDecimalDigit(char ch) {
1133         return ('0' <= ch && ch <= '9');
1134     }
1135 
1136     protected boolean isHexDigit(char ch) {
1137         return ('0' <= ch && ch <= '9')
1138                 || ('a' <= ch && ch <= 'f')
1139                 || ('A' <= ch && ch <= 'F');
1140     }
1141 
1142     protected boolean isUnquotedAttrValueTerminator(char ch) {
1143         switch (ch) {
1144             case '\f': case '\n': case '\r': case '\t':
1145             case ' ':
1146             case '"': case '\'': case '`':
1147             case '=': case '<': case '>':
1148                 return true;
1149             default:
1150                 return false;
1151         }
1152     }
1153 
1154     protected boolean isWhitespace(char ch) {
1155         return Character.isWhitespace(ch);
1156     }
1157 
1158     protected void skipWhitespace() {
1159         while (isWhitespace(ch)) {
1160             nextChar();
1161         }
1162     }
1163 
1164     /**
1165      * @param start position of first character of string
1166      * @param end position of character beyond last character to be included
1167      */
1168     String newString(int start, int end) {
1169         return new String(buf, start, end - start);
1170     }
1171 
1172     static abstract class TagParser {
1173         enum Kind { INLINE, BLOCK }
1174 
1175         final Kind kind;
1176         final DCTree.Kind treeKind;
1177         final boolean retainWhiteSpace;
1178 
1179 
1180         TagParser(Kind k, DCTree.Kind tk) {
1181             kind = k;
1182             treeKind = tk;
1183             retainWhiteSpace = false;
1184         }
1185 
1186         TagParser(Kind k, DCTree.Kind tk, boolean retainWhiteSpace) {
1187             kind = k;
1188             treeKind = tk;
1189             this.retainWhiteSpace = retainWhiteSpace;
1190         }
1191 
1192         Kind getKind() {
1193             return kind;
1194         }
1195 
1196         DCTree.Kind getTreeKind() {
1197             return treeKind;
1198         }
1199 
1200         abstract DCTree parse(int pos) throws ParseException;
1201     }
1202 
1203     /**
1204      * @see <a href="http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html#CHDJGIJB">Javadoc Tags</a>
1205      */
1206     private void initTagParsers() {
1207         TagParser[] parsers = {
1208             // @author name-text
1209             new TagParser(Kind.BLOCK, DCTree.Kind.AUTHOR) {
1210                 public DCTree parse(int pos) {
1211                     List<DCTree> name = blockContent();
1212                     return m.at(pos).newAuthorTree(name);
1213                 }
1214             },
1215 
1216             // {@code text}
1217             new TagParser(Kind.INLINE, DCTree.Kind.CODE, true) {
1218                 public DCTree parse(int pos) throws ParseException {
1219                     DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
1220                     nextChar();
1221                     return m.at(pos).newCodeTree((DCText) text);
1222                 }
1223             },
1224 
1225             // @deprecated deprecated-text
1226             new TagParser(Kind.BLOCK, DCTree.Kind.DEPRECATED) {
1227                 public DCTree parse(int pos) {
1228                     List<DCTree> reason = blockContent();
1229                     return m.at(pos).newDeprecatedTree(reason);
1230                 }
1231             },
1232 
1233             // {@docRoot}
1234             new TagParser(Kind.INLINE, DCTree.Kind.DOC_ROOT) {
1235                 public DCTree parse(int pos) throws ParseException {
1236                     if (ch == '}') {
1237                         nextChar();
1238                         return m.at(pos).newDocRootTree();
1239                     }
1240                     inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
1241                     nextChar();
1242                     throw new ParseException("dc.unexpected.content");
1243                 }
1244             },
1245 
1246             // @exception class-name description
1247             new TagParser(Kind.BLOCK, DCTree.Kind.EXCEPTION) {
1248                 public DCTree parse(int pos) throws ParseException {
1249                     skipWhitespace();
1250                     DCReference ref = reference(false);
1251                     List<DCTree> description = blockContent();
1252                     return m.at(pos).newExceptionTree(ref, description);
1253                 }
1254             },
1255 
1256             // @hidden hidden-text
1257             new TagParser(Kind.BLOCK, DCTree.Kind.HIDDEN) {
1258                 public DCTree parse(int pos) {
1259                     List<DCTree> reason = blockContent();
1260                     return m.at(pos).newHiddenTree(reason);
1261                 }
1262             },
1263 
1264             // @index search-term options-description
1265             new TagParser(Kind.INLINE, DCTree.Kind.INDEX) {
1266                 public DCTree parse(int pos) throws ParseException {
1267                     skipWhitespace();
1268                     if (ch == '}') {
1269                         throw new ParseException("dc.no.content");
1270                     }
1271                     DCTree term = ch == '"' ? quotedString() : inlineWord();
1272                     if (term == null) {
1273                         throw new ParseException("dc.no.content");
1274                     }
1275                     skipWhitespace();
1276                     List<DCTree> description = List.nil();
1277                     if (ch != '}') {
1278                         description = inlineContent();
1279                     } else {
1280                         nextChar();
1281                     }
1282                     return m.at(pos).newIndexTree(term, description);
1283                 }
1284             },
1285 
1286             // {@inheritDoc}
1287             new TagParser(Kind.INLINE, DCTree.Kind.INHERIT_DOC) {
1288                 public DCTree parse(int pos) throws ParseException {
1289                     if (ch == '}') {
1290                         nextChar();
1291                         return m.at(pos).newInheritDocTree();
1292                     }
1293                     inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
1294                     nextChar();
1295                     throw new ParseException("dc.unexpected.content");
1296                 }
1297             },
1298 
1299             // {@link package.class#member label}
1300             new TagParser(Kind.INLINE, DCTree.Kind.LINK) {
1301                 public DCTree parse(int pos) throws ParseException {
1302                     DCReference ref = reference(true);
1303                     List<DCTree> label = inlineContent();
1304                     return m.at(pos).newLinkTree(ref, label);
1305                 }
1306             },
1307 
1308             // {@linkplain package.class#member label}
1309             new TagParser(Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
1310                 public DCTree parse(int pos) throws ParseException {
1311                     DCReference ref = reference(true);
1312                     List<DCTree> label = inlineContent();
1313                     return m.at(pos).newLinkPlainTree(ref, label);
1314                 }
1315             },
1316 
1317             // {@literal text}
1318             new TagParser(Kind.INLINE, DCTree.Kind.LITERAL, true) {
1319                 public DCTree parse(int pos) throws ParseException {
1320                     DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
1321                     nextChar();
1322                     return m.at(pos).newLiteralTree((DCText) text);
1323                 }
1324             },
1325 
1326             // {@getter text}
1327             new AccessorParser(DCTree.Kind.GETTER),
1328 
1329             // {@getter text}
1330             new AccessorParser(DCTree.Kind.SETTER),
1331 
1332             // @param parameter-name description
1333             new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) {
1334                 public DCTree parse(int pos) throws ParseException {
1335                     skipWhitespace();
1336 
1337                     boolean typaram = false;
1338                     if (ch == '<') {
1339                         typaram = true;
1340                         nextChar();
1341                     }
1342 
1343                     DCIdentifier id = identifier();
1344 
1345                     if (typaram) {
1346                         if (ch != '>')
1347                             throw new ParseException("dc.gt.expected");
1348                         nextChar();
1349                     }
1350 
1351                     skipWhitespace();
1352                     List<DCTree> desc = blockContent();
1353                     return m.at(pos).newParamTree(typaram, id, desc);
1354                 }
1355             },
1356 
1357             // @provides service-name description
1358             new TagParser(Kind.BLOCK, DCTree.Kind.PROVIDES) {
1359                 public DCTree parse(int pos) throws ParseException {
1360                     skipWhitespace();
1361                     DCReference ref = reference(true);
1362                     List<DCTree> description = blockContent();
1363                     return m.at(pos).newProvidesTree(ref, description);
1364                 }
1365             },
1366 
1367             // @return description
1368             new TagParser(Kind.BLOCK, DCTree.Kind.RETURN) {
1369                 public DCTree parse(int pos) {
1370                     List<DCTree> description = blockContent();
1371                     return m.at(pos).newReturnTree(description);
1372                 }
1373             },
1374 
1375             // @see reference | quoted-string | HTML
1376             new TagParser(Kind.BLOCK, DCTree.Kind.SEE) {
1377                 public DCTree parse(int pos) throws ParseException {
1378                     skipWhitespace();
1379                     switch (ch) {
1380                         case '"':
1381                             DCText string = quotedString();
1382                             if (string != null) {
1383                                 skipWhitespace();
1384                                 if (ch == '@'
1385                                         || ch == EOI && bp == buf.length - 1) {
1386                                     return m.at(pos).newSeeTree(List.<DCTree>of(string));
1387                                 }
1388                             }
1389                             break;
1390 
1391                         case '<':
1392                             List<DCTree> html = blockContent();
1393                             if (html != null)
1394                                 return m.at(pos).newSeeTree(html);
1395                             break;
1396 
1397                         case '@':
1398                             if (newline)
1399                                 throw new ParseException("dc.no.content");
1400                             break;
1401 
1402                         case EOI:
1403                             if (bp == buf.length - 1)
1404                                 throw new ParseException("dc.no.content");
1405                             break;
1406 
1407                         default:
1408                             if (isJavaIdentifierStart(ch) || ch == '#') {
1409                                 DCReference ref = reference(true);
1410                                 List<DCTree> description = blockContent();
1411                                 return m.at(pos).newSeeTree(description.prepend(ref));
1412                             }
1413                     }
1414                     throw new ParseException("dc.unexpected.content");
1415                 }
1416             },
1417 
1418             // @serialData data-description
1419             new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_DATA) {
1420                 public DCTree parse(int pos) {
1421                     List<DCTree> description = blockContent();
1422                     return m.at(pos).newSerialDataTree(description);
1423                 }
1424             },
1425 
1426             // @serialField field-name field-type description
1427             new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_FIELD) {
1428                 public DCTree parse(int pos) throws ParseException {
1429                     skipWhitespace();
1430                     DCIdentifier name = identifier();
1431                     skipWhitespace();
1432                     DCReference type = reference(false);
1433                     List<DCTree> description = null;
1434                     if (isWhitespace(ch)) {
1435                         skipWhitespace();
1436                         description = blockContent();
1437                     }
1438                     return m.at(pos).newSerialFieldTree(name, type, description);
1439                 }
1440             },
1441 
1442             // @serial field-description | include | exclude
1443             new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL) {
1444                 public DCTree parse(int pos) {
1445                     List<DCTree> description = blockContent();
1446                     return m.at(pos).newSerialTree(description);
1447                 }
1448             },
1449 
1450             // @since since-text
1451             new TagParser(Kind.BLOCK, DCTree.Kind.SINCE) {
1452                 public DCTree parse(int pos) {
1453                     List<DCTree> description = blockContent();
1454                     return m.at(pos).newSinceTree(description);
1455                 }
1456             },
1457 
1458             // @summary summary-text
1459             new TagParser(Kind.INLINE, DCTree.Kind.SUMMARY) {
1460                 public DCTree parse(int pos) throws ParseException {
1461                     List<DCTree> summary = inlineContent();
1462                     return m.at(pos).newSummaryTree(summary);
1463                 }
1464             },
1465 
1466             // @systemProperty property-name
1467             new TagParser(Kind.INLINE, DCTree.Kind.SYSTEM_PROPERTY) {
1468                 public DCTree parse(int pos) throws ParseException {
1469                     skipWhitespace();
1470                     if (ch == '}') {
1471                         throw new ParseException("dc.no.content");
1472                     }
1473                     Name propertyName = readSystemPropertyName();
1474                     if (propertyName == null) {
1475                         throw new ParseException("dc.no.content");
1476                     }
1477                     skipWhitespace();
1478                     if (ch != '}') {
1479                         nextChar();
1480                         throw new ParseException("dc.unexpected.content");
1481                     } else {
1482                         nextChar();
1483                         return m.at(pos).newSystemPropertyTree(propertyName);
1484                     }
1485                 }
1486             },
1487 
1488             // @throws class-name description
1489             new TagParser(Kind.BLOCK, DCTree.Kind.THROWS) {
1490                 public DCTree parse(int pos) throws ParseException {
1491                     skipWhitespace();
1492                     DCReference ref = reference(false);
1493                     List<DCTree> description = blockContent();
1494                     return m.at(pos).newThrowsTree(ref, description);
1495                 }
1496             },
1497 
1498             // @uses service-name description
1499             new TagParser(Kind.BLOCK, DCTree.Kind.USES) {
1500                 public DCTree parse(int pos) throws ParseException {
1501                     skipWhitespace();
1502                     DCReference ref = reference(true);
1503                     List<DCTree> description = blockContent();
1504                     return m.at(pos).newUsesTree(ref, description);
1505                 }
1506             },
1507 
1508             // {@value package.class#field}
1509             new TagParser(Kind.INLINE, DCTree.Kind.VALUE) {
1510                 public DCTree parse(int pos) throws ParseException {
1511                     DCReference ref = reference(true);
1512                     skipWhitespace();
1513                     if (ch == '}') {
1514                         nextChar();
1515                         return m.at(pos).newValueTree(ref);
1516                     }
1517                     nextChar();
1518                     throw new ParseException("dc.unexpected.content");
1519                 }
1520             },
1521 
1522             // @version version-text
1523             new TagParser(Kind.BLOCK, DCTree.Kind.VERSION) {
1524                 public DCTree parse(int pos) {
1525                     List<DCTree> description = blockContent();
1526                     return m.at(pos).newVersionTree(description);
1527                 }
1528             },
1529         };
1530 
1531         tagParsers = new HashMap<>();
1532         for (TagParser p: parsers)
1533             tagParsers.put(names.fromString(p.getTreeKind().tagName), p);
1534 
1535     }
1536 
1537     class AccessorParser extends TagParser {
1538         AccessorParser(DocTree.Kind kind) {
1539             super(Kind.BLOCK, kind, true);
1540         }
1541 
1542         public DCTree parse(int pos) throws ParseException {
1543             List<DCTree> desc = blockContent();
1544             return m.at(pos).newAccessorTree(treeKind, desc);
1545         }
1546     }
1547 }