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