DocCommentParser.java revision 3060:23f76aadbb36
185587Sobrien/*
285587Sobrien * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
385587Sobrien * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
485587Sobrien *
585587Sobrien * This code is free software; you can redistribute it and/or modify it
685587Sobrien * under the terms of the GNU General Public License version 2 only, as
785587Sobrien * published by the Free Software Foundation.  Oracle designates this
885587Sobrien * particular file as subject to the "Classpath" exception as provided
985587Sobrien * by Oracle in the LICENSE file that accompanied this code.
1085587Sobrien *
1185587Sobrien * This code is distributed in the hope that it will be useful, but WITHOUT
1285587Sobrien * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1385587Sobrien * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1485587Sobrien * version 2 for more details (a copy is included in the LICENSE file that
1585587Sobrien * accompanied this code).
1685587Sobrien *
1785587Sobrien * You should have received a copy of the GNU General Public License version
1885587Sobrien * 2 along with this work; if not, write to the Free Software Foundation,
1985587Sobrien * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2085587Sobrien *
2185587Sobrien * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2285587Sobrien * or visit www.oracle.com if you need additional information or have any
2385587Sobrien * questions.
2485587Sobrien */
2585587Sobrien
2685587Sobrienpackage com.sun.tools.javac.parser;
2785587Sobrien
2885587Sobrienimport java.text.BreakIterator;
2985587Sobrienimport java.util.HashMap;
3085587Sobrienimport java.util.Map;
3185587Sobrien
3285587Sobrienimport com.sun.source.doctree.AttributeTree.ValueKind;
3385587Sobrienimport com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
3485587Sobrienimport com.sun.tools.javac.parser.Tokens.Comment;
3585587Sobrienimport com.sun.tools.javac.parser.Tokens.TokenKind;
3685587Sobrienimport com.sun.tools.javac.tree.DCTree;
3785587Sobrienimport com.sun.tools.javac.tree.DCTree.DCAttribute;
3885587Sobrienimport com.sun.tools.javac.tree.DCTree.DCDocComment;
3985587Sobrienimport com.sun.tools.javac.tree.DCTree.DCEndPosTree;
4085587Sobrienimport com.sun.tools.javac.tree.DCTree.DCErroneous;
4185587Sobrienimport com.sun.tools.javac.tree.DCTree.DCIdentifier;
4285587Sobrienimport com.sun.tools.javac.tree.DCTree.DCReference;
4385587Sobrienimport com.sun.tools.javac.tree.DCTree.DCText;
4485587Sobrienimport com.sun.tools.javac.tree.DocTreeMaker;
4585587Sobrienimport com.sun.tools.javac.tree.JCTree;
4685587Sobrienimport com.sun.tools.javac.util.DiagnosticSource;
4785587Sobrienimport com.sun.tools.javac.util.List;
4885587Sobrienimport com.sun.tools.javac.util.ListBuffer;
4985587Sobrienimport com.sun.tools.javac.util.Log;
5085587Sobrienimport com.sun.tools.javac.util.Name;
5185587Sobrienimport com.sun.tools.javac.util.Names;
5285587Sobrienimport com.sun.tools.javac.util.Position;
5385587Sobrien
5485587Sobrienimport static com.sun.tools.javac.util.LayoutCharacters.*;
5585587Sobrien
5685587Sobrien/**
5785587Sobrien *
5885587Sobrien *  <p><b>This is NOT part of any supported API.
5985587Sobrien *  If you write code that depends on this, you do so at your own risk.
6085587Sobrien *  This code and its internal interfaces are subject to change or
6185587Sobrien *  deletion without notice.</b>
6285587Sobrien */
6385587Sobrienpublic class DocCommentParser {
6485587Sobrien    static class ParseException extends Exception {
6585587Sobrien        private static final long serialVersionUID = 0;
6685587Sobrien        ParseException(String key) {
6785587Sobrien            super(key);
6885587Sobrien        }
6985587Sobrien    }
7085587Sobrien
7185587Sobrien    final ParserFactory fac;
7285587Sobrien    final DiagnosticSource diagSource;
7385587Sobrien    final Comment comment;
7485587Sobrien    final DocTreeMaker m;
7585587Sobrien    final Names names;
7685587Sobrien
7785587Sobrien    BreakIterator sentenceBreaker;
78107806Sobrien
7985587Sobrien    /** The input buffer, index of most recent character read,
8085587Sobrien     *  index of one past last character in buffer.
8185587Sobrien     */
8285587Sobrien    protected char[] buf;
8385587Sobrien    protected int bp;
8485587Sobrien    protected int buflen;
8585587Sobrien
8685587Sobrien    /** The current character.
8785587Sobrien     */
8885587Sobrien    protected char ch;
8985587Sobrien
9085587Sobrien    int textStart = -1;
9185587Sobrien    int lastNonWhite = -1;
9285587Sobrien    boolean newline = true;
9385587Sobrien
9485587Sobrien    Map<Name, TagParser> tagParsers;
9585587Sobrien
9690902Sdes    public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
9785587Sobrien        this.fac = fac;
9885587Sobrien        this.diagSource = diagSource;
9985587Sobrien        this.comment = comment;
10085587Sobrien        names = fac.names;
10185587Sobrien        m = fac.docTreeMaker;
10285587Sobrien        initTagParsers();
10385587Sobrien    }
10485587Sobrien
10585587Sobrien    public DocCommentParser(ParserFactory fac) {
10685587Sobrien        this(fac, null, null);
10785587Sobrien    }
10885587Sobrien
10985587Sobrien    public DCDocComment parse() {
11085587Sobrien        String c = comment.getText();
11185587Sobrien        buf = new char[c.length() + 1];
11285587Sobrien        c.getChars(0, c.length(), buf, 0);
11385587Sobrien        buf[buf.length - 1] = EOI;
11485587Sobrien        buflen = buf.length - 1;
11585587Sobrien        bp = -1;
11685587Sobrien        nextChar();
11785587Sobrien
11885587Sobrien        List<DCTree> body = blockContent();
11985587Sobrien        List<DCTree> tags = blockTags();
120107806Sobrien        int pos = !body.isEmpty()
12185587Sobrien                ? body.head.pos
12285587Sobrien                : !tags.isEmpty() ? tags.head.pos : Position.NOPOS;
12385587Sobrien
12485587Sobrien        DCDocComment dc = m.at(pos).DocComment(comment, body, tags);
12585587Sobrien        return dc;
12685587Sobrien    }
12785587Sobrien
12885587Sobrien    void nextChar() {
12985587Sobrien        ch = buf[bp < buflen ? ++bp : buflen];
13085587Sobrien        switch (ch) {
13185587Sobrien            case '\f': case '\n': case '\r':
13285587Sobrien                newline = true;
13385587Sobrien        }
13485587Sobrien    }
13585587Sobrien
13685587Sobrien    /**
13785587Sobrien     * Read block content, consisting of text, html and inline tags.
13885587Sobrien     * Terminated by the end of input, or the beginning of the next block tag:
13985587Sobrien     * i.e. @ as the first non-whitespace character on a line.
14085587Sobrien     */
14185587Sobrien    @SuppressWarnings("fallthrough")
14285587Sobrien    protected List<DCTree> blockContent() {
14385587Sobrien        ListBuffer<DCTree> trees = new ListBuffer<>();
14485587Sobrien        textStart = -1;
14585587Sobrien
14685587Sobrien        loop:
14785587Sobrien        while (bp < buflen) {
14885587Sobrien            switch (ch) {
14985587Sobrien                case '\n': case '\r': case '\f':
15085587Sobrien                    newline = true;
15185587Sobrien                    // fallthrough
15285587Sobrien
15385587Sobrien                case ' ': case '\t':
15485587Sobrien                    nextChar();
15585587Sobrien                    break;
15685587Sobrien
15785587Sobrien                case '&':
15885587Sobrien                    entity(trees);
15985587Sobrien                    break;
16085587Sobrien
16185587Sobrien                case '<':
16285587Sobrien                    newline = false;
16385587Sobrien                    addPendingText(trees, bp - 1);
16485587Sobrien                    trees.add(html());
16585587Sobrien                    if (textStart == -1) {
16685587Sobrien                        textStart = bp;
16785587Sobrien                        lastNonWhite = -1;
16885587Sobrien                    }
16985587Sobrien                    break;
17085587Sobrien
17185587Sobrien                case '>':
17285587Sobrien                    newline = false;
17385587Sobrien                    addPendingText(trees, bp - 1);
17485587Sobrien                    trees.add(m.at(bp).Erroneous(newString(bp, bp+1), diagSource, "dc.bad.gt"));
17585587Sobrien                    nextChar();
17685587Sobrien                    if (textStart == -1) {
17785587Sobrien                        textStart = bp;
17885587Sobrien                        lastNonWhite = -1;
17985587Sobrien                    }
18085587Sobrien                    break;
18185587Sobrien
18285587Sobrien                case '{':
18385587Sobrien                    inlineTag(trees);
18485587Sobrien                    break;
18585587Sobrien
18685587Sobrien                case '@':
18785587Sobrien                    if (newline) {
18885587Sobrien                        addPendingText(trees, lastNonWhite);
18985587Sobrien                        break loop;
19085587Sobrien                    }
19185587Sobrien                    // fallthrough
19285587Sobrien
19385587Sobrien                default:
19485587Sobrien                    newline = false;
19585587Sobrien                    if (textStart == -1)
19685587Sobrien                        textStart = bp;
19785587Sobrien                    lastNonWhite = bp;
19885587Sobrien                    nextChar();
19985587Sobrien            }
20085587Sobrien        }
20185587Sobrien
20285587Sobrien        if (lastNonWhite != -1)
20385587Sobrien            addPendingText(trees, lastNonWhite);
20485587Sobrien
20585587Sobrien        return trees.toList();
20685587Sobrien    }
20785587Sobrien
20885587Sobrien    /**
20985587Sobrien     * Read a series of block tags, including their content.
21085587Sobrien     * Standard tags parse their content appropriately.
21185587Sobrien     * Non-standard tags are represented by {@link UnknownBlockTag}.
21285587Sobrien     */
21385587Sobrien    protected List<DCTree> blockTags() {
21485587Sobrien        ListBuffer<DCTree> tags = new ListBuffer<>();
21585587Sobrien        while (ch == '@')
21685587Sobrien            tags.add(blockTag());
21785587Sobrien        return tags.toList();
21885587Sobrien    }
21985587Sobrien
22085587Sobrien    /**
22185587Sobrien     * Read a single block tag, including its content.
22285587Sobrien     * Standard tags parse their content appropriately.
22385587Sobrien     * Non-standard tags are represented by {@link UnknownBlockTag}.
22485587Sobrien     */
22585587Sobrien    protected DCTree blockTag() {
22685587Sobrien        int p = bp;
22785587Sobrien        try {
22885587Sobrien            nextChar();
22985587Sobrien            if (isIdentifierStart(ch)) {
23085587Sobrien                Name name = readTagName();
23185587Sobrien                TagParser tp = tagParsers.get(name);
23285587Sobrien                if (tp == null) {
23385587Sobrien                    List<DCTree> content = blockContent();
23485587Sobrien                    return m.at(p).UnknownBlockTag(name, content);
23585587Sobrien                } else {
23685587Sobrien                    switch (tp.getKind()) {
23785587Sobrien                        case BLOCK:
23885587Sobrien                            return tp.parse(p);
23985587Sobrien                        case INLINE:
24085587Sobrien                            return erroneous("dc.bad.inline.tag", p);
24185587Sobrien                    }
24285587Sobrien                }
24385587Sobrien            }
24485587Sobrien            blockContent();
24585587Sobrien
24685587Sobrien            return erroneous("dc.no.tag.name", p);
24785587Sobrien        } catch (ParseException e) {
24885587Sobrien            blockContent();
24985587Sobrien            return erroneous(e.getMessage(), p);
25085587Sobrien        }
25185587Sobrien    }
25285587Sobrien
25385587Sobrien    protected void inlineTag(ListBuffer<DCTree> list) {
25485587Sobrien        newline = false;
25585587Sobrien        nextChar();
25685587Sobrien        if (ch == '@') {
25785587Sobrien            addPendingText(list, bp - 2);
25885587Sobrien            list.add(inlineTag());
25985587Sobrien            textStart = bp;
26085587Sobrien            lastNonWhite = -1;
26185587Sobrien        } else {
26285587Sobrien            if (textStart == -1)
26385587Sobrien                textStart = bp - 1;
26485587Sobrien            lastNonWhite = bp;
26585587Sobrien        }
26685587Sobrien    }
26785587Sobrien
26885587Sobrien    /**
26985587Sobrien     * Read a single inline tag, including its content.
27085587Sobrien     * Standard tags parse their content appropriately.
27185587Sobrien     * Non-standard tags are represented by {@link UnknownBlockTag}.
27285587Sobrien     * Malformed tags may be returned as {@link Erroneous}.
27385587Sobrien     */
27485587Sobrien    protected DCTree inlineTag() {
27585587Sobrien        int p = bp - 1;
27685587Sobrien        try {
27785587Sobrien            nextChar();
27885587Sobrien            if (isIdentifierStart(ch)) {
27985587Sobrien                Name name = readTagName();
28085587Sobrien                TagParser tp = tagParsers.get(name);
28185587Sobrien
28285587Sobrien                if (tp == null) {
28385587Sobrien                    skipWhitespace();
28485587Sobrien                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
285112336Sobrien                    if (text != null) {
286112336Sobrien                        nextChar();
287112336Sobrien                        return m.at(p).UnknownInlineTag(name, List.of(text)).setEndPos(bp);
288112336Sobrien                    }
289112336Sobrien                } else {
290112336Sobrien                    if (!tp.retainWhiteSpace) {
291112336Sobrien                        skipWhitespace();
292112336Sobrien                    }
293112336Sobrien                    if (tp.getKind() == TagParser.Kind.INLINE) {
294112336Sobrien                        DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
295112336Sobrien                        if (tree != null) {
296112336Sobrien                            return tree.setEndPos(bp);
297112336Sobrien                        }
298112336Sobrien                    } else { // handle block tags (ex: @see) in inline content
299107806Sobrien                        inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
300107806Sobrien                        nextChar();
30185587Sobrien                    }
302112336Sobrien                }
30385587Sobrien            }
30485587Sobrien            return erroneous("dc.no.tag.name", p);
30585587Sobrien        } catch (ParseException e) {
30685587Sobrien            return erroneous(e.getMessage(), p);
30785587Sobrien        }
30885587Sobrien    }
30985587Sobrien
31085587Sobrien    private static enum WhitespaceRetentionPolicy {
31185587Sobrien        RETAIN_ALL,
31285587Sobrien        REMOVE_FIRST_SPACE,
31385587Sobrien        REMOVE_ALL
31485587Sobrien    }
31585587Sobrien
31685587Sobrien    /**
31785587Sobrien     * Read plain text content of an inline tag.
31885587Sobrien     * Matching pairs of { } are skipped; the text is terminated by the first
31985587Sobrien     * unmatched }. It is an error if the beginning of the next tag is detected.
32085587Sobrien     */
321112336Sobrien    private DCTree inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
32285587Sobrien        switch (whitespacePolicy) {
32385587Sobrien            case REMOVE_ALL:
32485587Sobrien                skipWhitespace();
32585587Sobrien                break;
326112336Sobrien            case REMOVE_FIRST_SPACE:
327112336Sobrien                if (ch == ' ')
328112336Sobrien                    nextChar();
329112336Sobrien                break;
33085587Sobrien            case RETAIN_ALL:
33185587Sobrien            default:
332112336Sobrien                // do nothing
33385587Sobrien                break;
33485587Sobrien
33585587Sobrien        }
33685587Sobrien        int pos = bp;
33785587Sobrien        int depth = 1;
33885587Sobrien
33985587Sobrien        loop:
34085587Sobrien        while (bp < buflen) {
34185587Sobrien            switch (ch) {
34285587Sobrien                case '\n': case '\r': case '\f':
34385587Sobrien                    newline = true;
34485587Sobrien                    break;
34585587Sobrien
34685587Sobrien                case ' ': case '\t':
34785587Sobrien                    break;
34885587Sobrien
349107806Sobrien                case '{':
35085587Sobrien                    newline = false;
35185587Sobrien                    lastNonWhite = bp;
35285587Sobrien                    depth++;
35385587Sobrien                    break;
35485587Sobrien
35585587Sobrien                case '}':
35685587Sobrien                    if (--depth == 0) {
35785587Sobrien                        return m.at(pos).Text(newString(pos, bp));
35885587Sobrien                    }
35985587Sobrien                    newline = false;
36085587Sobrien                    lastNonWhite = bp;
36185587Sobrien                    break;
36285587Sobrien
36385587Sobrien                case '@':
36485587Sobrien                    if (newline)
36585587Sobrien                        break loop;
36685587Sobrien                    newline = false;
36785587Sobrien                    lastNonWhite = bp;
36885587Sobrien                    break;
36985587Sobrien
37085587Sobrien                default:
37185587Sobrien                    newline = false;
37285587Sobrien                    lastNonWhite = bp;
37385587Sobrien                    break;
37485587Sobrien            }
37585587Sobrien            nextChar();
37685587Sobrien        }
37785587Sobrien        throw new ParseException("dc.unterminated.inline.tag");
37885587Sobrien    }
37985587Sobrien
38085587Sobrien    /**
38185587Sobrien     * Read Java class name, possibly followed by member
38285587Sobrien     * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
38385587Sobrien     * unmatched }. It is an error if the beginning of the next tag is detected.
38485587Sobrien     */
38585587Sobrien    // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
38685587Sobrien    // TODO: improve quality of parse to forbid bad constructions.
38785587Sobrien    @SuppressWarnings("fallthrough")
38885587Sobrien    protected DCReference reference(boolean allowMember) throws ParseException {
38985587Sobrien        int pos = bp;
39085587Sobrien        int depth = 0;
39185587Sobrien
39285587Sobrien        // scan to find the end of the signature, by looking for the first
39385587Sobrien        // whitespace not enclosed in () or <>, or the end of the tag
39485587Sobrien        loop:
39585587Sobrien        while (bp < buflen) {
39685587Sobrien            switch (ch) {
39785587Sobrien                case '\n': case '\r': case '\f':
39885587Sobrien                    newline = true;
39985587Sobrien                    // fallthrough
40085587Sobrien
40185587Sobrien                case ' ': case '\t':
40285587Sobrien                    if (depth == 0)
40385587Sobrien                        break loop;
40485587Sobrien                    break;
40585587Sobrien
40685587Sobrien                case '(':
40785587Sobrien                case '<':
40885587Sobrien                    newline = false;
40985587Sobrien                    depth++;
41085587Sobrien                    break;
41185587Sobrien
41285587Sobrien                case ')':
41385587Sobrien                case '>':
41485587Sobrien                    newline = false;
41585587Sobrien                    --depth;
41685587Sobrien                    break;
41785587Sobrien
41885587Sobrien                case '}':
41985587Sobrien                    if (bp == pos)
42085587Sobrien                        return null;
42185587Sobrien                    newline = false;
42285587Sobrien                    break loop;
42385587Sobrien
42485587Sobrien                case '@':
42585587Sobrien                    if (newline)
42685587Sobrien                        break loop;
42785587Sobrien                    // fallthrough
42885587Sobrien
42985587Sobrien                default:
43085587Sobrien                    newline = false;
43185587Sobrien
43285587Sobrien            }
43385587Sobrien            nextChar();
43485587Sobrien        }
43585587Sobrien
43685587Sobrien        if (depth != 0)
43785587Sobrien            throw new ParseException("dc.unterminated.signature");
43885587Sobrien
43985587Sobrien        String sig = newString(pos, bp);
44085587Sobrien
44185587Sobrien        // Break sig apart into qualifiedExpr member paramTypes.
44285587Sobrien        JCTree qualExpr;
44385587Sobrien        Name member;
44485587Sobrien        List<JCTree> paramTypes;
44585587Sobrien
44685587Sobrien        Log.DeferredDiagnosticHandler deferredDiagnosticHandler
44785587Sobrien                = new Log.DeferredDiagnosticHandler(fac.log);
44885587Sobrien
44985587Sobrien        try {
45085587Sobrien            int hash = sig.indexOf("#");
45185587Sobrien            int lparen = sig.indexOf("(", hash + 1);
45285587Sobrien            if (hash == -1) {
45385587Sobrien                if (lparen == -1) {
45485587Sobrien                    qualExpr = parseType(sig);
45585587Sobrien                    member = null;
45685587Sobrien                } else {
45785587Sobrien                    qualExpr = null;
45885587Sobrien                    member = parseMember(sig.substring(0, lparen));
45985587Sobrien                }
46085587Sobrien            } else {
46185587Sobrien                qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
46285587Sobrien                if (lparen == -1)
46385587Sobrien                    member = parseMember(sig.substring(hash + 1));
46485587Sobrien                else
46585587Sobrien                    member = parseMember(sig.substring(hash + 1, lparen));
46685587Sobrien            }
467107806Sobrien
46885587Sobrien            if (lparen < 0) {
46985587Sobrien                paramTypes = null;
47085587Sobrien            } else {
47185587Sobrien                int rparen = sig.indexOf(")", lparen);
47285587Sobrien                if (rparen != sig.length() - 1)
47385587Sobrien                    throw new ParseException("dc.ref.bad.parens");
47485587Sobrien                paramTypes = parseParams(sig.substring(lparen + 1, rparen));
47585587Sobrien            }
47685587Sobrien
477107806Sobrien            if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
47885587Sobrien                throw new ParseException("dc.ref.syntax.error");
47985587Sobrien
48085587Sobrien        } finally {
48185587Sobrien            fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
48285587Sobrien        }
48385587Sobrien
48485587Sobrien        return m.at(pos).Reference(sig, qualExpr, member, paramTypes).setEndPos(bp);
48585587Sobrien    }
48685587Sobrien
48785587Sobrien    JCTree parseType(String s) throws ParseException {
48885587Sobrien        JavacParser p = fac.newParser(s, false, false, false);
48985587Sobrien        JCTree tree = p.parseType();
49085587Sobrien        if (p.token().kind != TokenKind.EOF)
49185587Sobrien            throw new ParseException("dc.ref.unexpected.input");
49285587Sobrien        return tree;
49385587Sobrien    }
49485587Sobrien
49585587Sobrien    Name parseMember(String s) throws ParseException {
496107806Sobrien        JavacParser p = fac.newParser(s, false, false, false);
49785587Sobrien        Name name = p.ident();
49885587Sobrien        if (p.token().kind != TokenKind.EOF)
49985587Sobrien            throw new ParseException("dc.ref.unexpected.input");
50085587Sobrien        return name;
50185587Sobrien    }
50285587Sobrien
50385587Sobrien    List<JCTree> parseParams(String s) throws ParseException {
50485587Sobrien        if (s.trim().isEmpty())
50585587Sobrien            return List.nil();
50685587Sobrien
50785587Sobrien        JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
50885587Sobrien        ListBuffer<JCTree> paramTypes = new ListBuffer<>();
50985587Sobrien        paramTypes.add(p.parseType());
51085587Sobrien
51185587Sobrien        if (p.token().kind == TokenKind.IDENTIFIER)
51285587Sobrien            p.nextToken();
51385587Sobrien
51485587Sobrien        while (p.token().kind == TokenKind.COMMA) {
51585587Sobrien            p.nextToken();
51685587Sobrien            paramTypes.add(p.parseType());
51785587Sobrien
51885587Sobrien            if (p.token().kind == TokenKind.IDENTIFIER)
51985587Sobrien                p.nextToken();
52085587Sobrien        }
52185587Sobrien
52285587Sobrien        if (p.token().kind != TokenKind.EOF)
52385587Sobrien            throw new ParseException("dc.ref.unexpected.input");
52485587Sobrien
52585587Sobrien        return paramTypes.toList();
52685587Sobrien    }
52785587Sobrien
52885587Sobrien    /**
52985587Sobrien     * Read Java identifier
53085587Sobrien     * Matching pairs of { } are skipped; the text is terminated by the first
53185587Sobrien     * unmatched }. It is an error if the beginning of the next tag is detected.
53285587Sobrien     */
53385587Sobrien    @SuppressWarnings("fallthrough")
53485587Sobrien    protected DCIdentifier identifier() throws ParseException {
53585587Sobrien        skipWhitespace();
53685587Sobrien        int pos = bp;
53785587Sobrien
53885587Sobrien        if (isJavaIdentifierStart(ch)) {
53985587Sobrien            Name name = readJavaIdentifier();
54085587Sobrien            return m.at(pos).Identifier(name);
54185587Sobrien        }
54285587Sobrien
54385587Sobrien        throw new ParseException("dc.identifier.expected");
54485587Sobrien    }
54585587Sobrien
54685587Sobrien    /**
54785587Sobrien     * Read a quoted string.
54885587Sobrien     * It is an error if the beginning of the next tag is detected.
549107806Sobrien     */
55085587Sobrien    @SuppressWarnings("fallthrough")
55185587Sobrien    protected DCText quotedString() {
55285587Sobrien        int pos = bp;
55385587Sobrien        nextChar();
55485587Sobrien
55585587Sobrien        loop:
55685587Sobrien        while (bp < buflen) {
55785587Sobrien            switch (ch) {
55885587Sobrien                case '\n': case '\r': case '\f':
55985587Sobrien                    newline = true;
56085587Sobrien                    break;
56185587Sobrien
56285587Sobrien                case ' ': case '\t':
56385587Sobrien                    break;
56485587Sobrien
56585587Sobrien                case '"':
56685587Sobrien                    nextChar();
56785587Sobrien                    // trim trailing white-space?
56885587Sobrien                    return m.at(pos).Text(newString(pos, bp));
56985587Sobrien
57085587Sobrien                case '@':
57185587Sobrien                    if (newline)
57285587Sobrien                        break loop;
57385587Sobrien
57485587Sobrien            }
57585587Sobrien            nextChar();
57685587Sobrien        }
57785587Sobrien        return null;
57885587Sobrien    }
57985587Sobrien
58085587Sobrien    /**
58185587Sobrien     * Read general text content of an inline tag, including HTML entities and elements.
58285587Sobrien     * Matching pairs of { } are skipped; the text is terminated by the first
58385587Sobrien     * unmatched }. It is an error if the beginning of the next tag is detected.
58485587Sobrien     */
58585587Sobrien    @SuppressWarnings("fallthrough")
58685587Sobrien    protected List<DCTree> inlineContent() {
58785587Sobrien        ListBuffer<DCTree> trees = new ListBuffer<>();
58885587Sobrien
58985587Sobrien        skipWhitespace();
59085587Sobrien        int pos = bp;
59185587Sobrien        int depth = 1;
59285587Sobrien        textStart = -1;
59385587Sobrien
59485587Sobrien        loop:
59585587Sobrien        while (bp < buflen) {
59685587Sobrien
59785587Sobrien            switch (ch) {
59885587Sobrien                case '\n': case '\r': case '\f':
59985587Sobrien                    newline = true;
60085587Sobrien                    // fall through
601107806Sobrien
60285587Sobrien                case ' ': case '\t':
60385587Sobrien                    nextChar();
60485587Sobrien                    break;
60585587Sobrien
60685587Sobrien                case '&':
60785587Sobrien                    entity(trees);
608107806Sobrien                    break;
60985587Sobrien
610107806Sobrien                case '<':
611107806Sobrien                    newline = false;
61285587Sobrien                    addPendingText(trees, bp - 1);
61385587Sobrien                    trees.add(html());
61485587Sobrien                    break;
61585587Sobrien
61685587Sobrien                case '{':
61785587Sobrien                    newline = false;
61885587Sobrien                    depth++;
61985587Sobrien                    nextChar();
62085587Sobrien                    break;
62185587Sobrien
62285587Sobrien                case '}':
62385587Sobrien                    newline = false;
62485587Sobrien                    if (--depth == 0) {
62585587Sobrien                        addPendingText(trees, bp - 1);
62685587Sobrien                        nextChar();
62785587Sobrien                        return trees.toList();
62885587Sobrien                    }
62985587Sobrien                    nextChar();
63085587Sobrien                    break;
63185587Sobrien
63285587Sobrien                case '@':
63385587Sobrien                    if (newline)
63485587Sobrien                        break loop;
63585587Sobrien                    // fallthrough
63685587Sobrien
63785587Sobrien                default:
63885587Sobrien                    if (textStart == -1)
63985587Sobrien                        textStart = bp;
64085587Sobrien                    nextChar();
64185587Sobrien                    break;
64285587Sobrien            }
64385587Sobrien        }
64485587Sobrien
64585587Sobrien        return List.<DCTree>of(erroneous("dc.unterminated.inline.tag", pos));
64685587Sobrien    }
64785587Sobrien
64885587Sobrien    protected void entity(ListBuffer<DCTree> list) {
64985587Sobrien        newline = false;
65085587Sobrien        addPendingText(list, bp - 1);
65185587Sobrien        list.add(entity());
65285587Sobrien        if (textStart == -1) {
65385587Sobrien            textStart = bp;
65485587Sobrien            lastNonWhite = -1;
65585587Sobrien        }
65685587Sobrien    }
65785587Sobrien
65885587Sobrien    /**
65985587Sobrien     * Read an HTML entity.
66085587Sobrien     * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
66185587Sobrien     */
66285587Sobrien    protected DCTree entity() {
66385587Sobrien        int p = bp;
66485587Sobrien        nextChar();
66585587Sobrien        Name name = null;
66685587Sobrien        if (ch == '#') {
66785587Sobrien            int namep = bp;
66885587Sobrien            nextChar();
66985587Sobrien            if (isDecimalDigit(ch)) {
67085587Sobrien                nextChar();
67185587Sobrien                while (isDecimalDigit(ch))
67285587Sobrien                    nextChar();
67385587Sobrien                name = names.fromChars(buf, namep, bp - namep);
67485587Sobrien            } else if (ch == 'x' || ch == 'X') {
67585587Sobrien                nextChar();
67685587Sobrien                if (isHexDigit(ch)) {
67785587Sobrien                    nextChar();
67885587Sobrien                    while (isHexDigit(ch))
67985587Sobrien                        nextChar();
68085587Sobrien                    name = names.fromChars(buf, namep, bp - namep);
68185587Sobrien                }
68285587Sobrien            }
68385587Sobrien        } else if (isIdentifierStart(ch)) {
68485587Sobrien            name = readIdentifier();
68585587Sobrien        }
68685587Sobrien
68785587Sobrien        if (name == null)
68885587Sobrien            return erroneous("dc.bad.entity", p);
68985587Sobrien        else {
69085587Sobrien            if (ch != ';')
69185587Sobrien                return erroneous("dc.missing.semicolon", p);
69285587Sobrien            nextChar();
69385587Sobrien            return m.at(p).Entity(name);
69485587Sobrien        }
69585587Sobrien    }
69685587Sobrien
69785587Sobrien    /**
69885587Sobrien     * Read the start or end of an HTML tag, or an HTML comment
69985587Sobrien     * {@literal <identifier attrs> } or {@literal </identifier> }
70085587Sobrien     */
70185587Sobrien    protected DCTree html() {
70285587Sobrien        int p = bp;
70385587Sobrien        nextChar();
70485587Sobrien        if (isIdentifierStart(ch)) {
70585587Sobrien            Name name = readIdentifier();
70690902Sdes            List<DCTree> attrs = htmlAttrs();
70790902Sdes            if (attrs != null) {
70890902Sdes                boolean selfClosing = false;
70990902Sdes                if (ch == '/') {
71090902Sdes                    nextChar();
71190902Sdes                    selfClosing = true;
71290902Sdes                }
71390902Sdes                if (ch == '>') {
71490902Sdes                    nextChar();
71590902Sdes                    DCTree dctree = m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp);
716112336Sobrien                    return dctree;
717112336Sobrien                }
718112336Sobrien            }
719112336Sobrien        } else if (ch == '/') {
720112336Sobrien            nextChar();
721112336Sobrien            if (isIdentifierStart(ch)) {
722112336Sobrien                Name name = readIdentifier();
723112336Sobrien                skipWhitespace();
724112336Sobrien                if (ch == '>') {
725112336Sobrien                    nextChar();
726112336Sobrien                    return m.at(p).EndElement(name);
727112336Sobrien                }
728112336Sobrien            }
729112336Sobrien        } else if (ch == '!') {
730112336Sobrien            nextChar();
731112336Sobrien            if (ch == '-') {
73290902Sdes                nextChar();
73390902Sdes                if (ch == '-') {
73490902Sdes                    nextChar();
735112336Sobrien                    while (bp < buflen) {
73690902Sdes                        int dash = 0;
737112336Sobrien                        while (ch == '-') {
738112336Sobrien                            dash++;
739112336Sobrien                            nextChar();
740112336Sobrien                        }
741112336Sobrien                        // strictly speaking, a comment should not contain "--"
742112336Sobrien                        // so dash > 2 is an error, dash == 2 implies ch == '>'
743112336Sobrien                        if (dash >= 2 && ch == '>') {
744112336Sobrien                            nextChar();
745112336Sobrien                            return m.at(p).Comment(newString(p, bp));
746112336Sobrien                        }
747112336Sobrien
748112336Sobrien                        nextChar();
74990902Sdes                    }
75090902Sdes                }
75190902Sdes            }
75290902Sdes        }
75385587Sobrien
75485587Sobrien        bp = p + 1;
75585587Sobrien        ch = buf[bp];
75685587Sobrien        return erroneous("dc.malformed.html", p);
75785587Sobrien    }
75885587Sobrien
75985587Sobrien    /**
76090902Sdes     * Read a series of HTML attributes, terminated by {@literal > }.
761112336Sobrien     * Each attribute is of the form {@literal identifier[=value] }.
76285587Sobrien     * "value" may be unquoted, single-quoted, or double-quoted.
76385587Sobrien     */
76485587Sobrien    protected List<DCTree> htmlAttrs() {
76585587Sobrien        ListBuffer<DCTree> attrs = new ListBuffer<>();
76685587Sobrien        skipWhitespace();
76785587Sobrien
76885587Sobrien        loop:
76985587Sobrien        while (isIdentifierStart(ch)) {
77085587Sobrien            int namePos = bp;
77185587Sobrien            Name name = readAttributeName();
77285587Sobrien            skipWhitespace();
77385587Sobrien            List<DCTree> value = null;
77485587Sobrien            ValueKind vkind = ValueKind.EMPTY;
77585587Sobrien            if (ch == '=') {
77685587Sobrien                ListBuffer<DCTree> v = new ListBuffer<>();
77785587Sobrien                nextChar();
77885587Sobrien                skipWhitespace();
77985587Sobrien                if (ch == '\'' || ch == '"') {
78085587Sobrien                    vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE;
78185587Sobrien                    char quote = ch;
78285587Sobrien                    nextChar();
78385587Sobrien                    textStart = bp;
78485587Sobrien                    while (bp < buflen && ch != quote) {
78585587Sobrien                        if (newline && ch == '@') {
78685587Sobrien                            attrs.add(erroneous("dc.unterminated.string", namePos));
78785587Sobrien                            // No point trying to read more.
78885587Sobrien                            // In fact, all attrs get discarded by the caller
78985587Sobrien                            // and superseded by a malformed.html node because
79085587Sobrien                            // the html tag itself is not terminated correctly.
79190902Sdes                            break loop;
79285587Sobrien                        }
79385587Sobrien                        attrValueChar(v);
79485587Sobrien                    }
79585587Sobrien                    addPendingText(v, bp - 1);
79685587Sobrien                    nextChar();
79785587Sobrien                } else {
79885587Sobrien                    vkind = ValueKind.UNQUOTED;
79985587Sobrien                    textStart = bp;
80085587Sobrien                    while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
80185587Sobrien                        attrValueChar(v);
80290902Sdes                    }
80390902Sdes                    addPendingText(v, bp - 1);
80490902Sdes                }
80590902Sdes                skipWhitespace();
80690902Sdes                value = v.toList();
80790902Sdes            }
80890902Sdes            DCAttribute attr = m.at(namePos).Attribute(name, vkind, value);
80990902Sdes            attrs.add(attr);
810112336Sobrien        }
811112336Sobrien
812112336Sobrien        return attrs.toList();
813112336Sobrien    }
814112336Sobrien
815112336Sobrien    protected void attrValueChar(ListBuffer<DCTree> list) {
816112336Sobrien        switch (ch) {
817112336Sobrien            case '&':
81890902Sdes                entity(list);
81990902Sdes                break;
82085587Sobrien
82185587Sobrien            case '{':
82285587Sobrien                inlineTag(list);
82385587Sobrien                break;
82485587Sobrien
82585587Sobrien            default:
82685587Sobrien                nextChar();
82785587Sobrien        }
82885587Sobrien    }
82985587Sobrien
83085587Sobrien    protected void addPendingText(ListBuffer<DCTree> list, int textEnd) {
83185587Sobrien        if (textStart != -1) {
83285587Sobrien            if (textStart <= textEnd) {
83385587Sobrien                list.add(m.at(textStart).Text(newString(textStart, textEnd + 1)));
83485587Sobrien            }
83585587Sobrien            textStart = -1;
83685587Sobrien        }
83785587Sobrien    }
83885587Sobrien
83985587Sobrien    protected DCErroneous erroneous(String code, int pos) {
84085587Sobrien        int i = bp - 1;
84185587Sobrien        loop:
84285587Sobrien        while (i > pos) {
84385587Sobrien            switch (buf[i]) {
84485587Sobrien                case '\f': case '\n': case '\r':
84585587Sobrien                    newline = true;
84685587Sobrien                    break;
84785587Sobrien                case '\t': case ' ':
84885587Sobrien                    break;
84985587Sobrien                default:
85085587Sobrien                    break loop;
85185587Sobrien            }
85285587Sobrien            i--;
85385587Sobrien        }
85485587Sobrien        textStart = -1;
85585587Sobrien        return m.at(pos).Erroneous(newString(pos, i + 1), diagSource, code);
85685587Sobrien    }
85785587Sobrien
85885587Sobrien    protected boolean isIdentifierStart(char ch) {
85985587Sobrien        return Character.isUnicodeIdentifierStart(ch);
86085587Sobrien    }
86185587Sobrien
86285587Sobrien    protected Name readIdentifier() {
86385587Sobrien        int start = bp;
86485587Sobrien        nextChar();
86585587Sobrien        while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
86685587Sobrien            nextChar();
86785587Sobrien        return names.fromChars(buf, start, bp - start);
86885587Sobrien    }
86985587Sobrien
87085587Sobrien    protected Name readAttributeName() {
87185587Sobrien        int start = bp;
87285587Sobrien        nextChar();
87385587Sobrien        while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
87485587Sobrien            nextChar();
87585587Sobrien        return names.fromChars(buf, start, bp - start);
87685587Sobrien    }
87785587Sobrien
87885587Sobrien    protected Name readTagName() {
87985587Sobrien        int start = bp;
88085587Sobrien        nextChar();
88185587Sobrien        while (bp < buflen
88285587Sobrien                && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
88385587Sobrien                || ch == '-' || ch == ':')) {
88485587Sobrien            nextChar();
88585587Sobrien        }
88685587Sobrien        return names.fromChars(buf, start, bp - start);
88785587Sobrien    }
88885587Sobrien
88985587Sobrien    protected boolean isJavaIdentifierStart(char ch) {
89085587Sobrien        return Character.isJavaIdentifierStart(ch);
89185587Sobrien    }
89285587Sobrien
89385587Sobrien    protected Name readJavaIdentifier() {
89485587Sobrien        int start = bp;
89585587Sobrien        nextChar();
89685587Sobrien        while (bp < buflen && Character.isJavaIdentifierPart(ch))
89785587Sobrien            nextChar();
89885587Sobrien        return names.fromChars(buf, start, bp - start);
89985587Sobrien    }
90085587Sobrien
90185587Sobrien    protected boolean isDecimalDigit(char ch) {
90285587Sobrien        return ('0' <= ch && ch <= '9');
90385587Sobrien    }
90485587Sobrien
90585587Sobrien    protected boolean isHexDigit(char ch) {
90685587Sobrien        return ('0' <= ch && ch <= '9')
90785587Sobrien                || ('a' <= ch && ch <= 'f')
90885587Sobrien                || ('A' <= ch && ch <= 'F');
90985587Sobrien    }
91085587Sobrien
91185587Sobrien    protected boolean isUnquotedAttrValueTerminator(char ch) {
91285587Sobrien        switch (ch) {
91385587Sobrien            case '\f': case '\n': case '\r': case '\t':
91485587Sobrien            case ' ':
91585587Sobrien            case '"': case '\'': case '`':
91685587Sobrien            case '=': case '<': case '>':
91785587Sobrien                return true;
91885587Sobrien            default:
91985587Sobrien                return false;
92085587Sobrien        }
92185587Sobrien    }
92285587Sobrien
92385587Sobrien    protected boolean isWhitespace(char ch) {
92485587Sobrien        return Character.isWhitespace(ch);
92585587Sobrien    }
92685587Sobrien
92785587Sobrien    protected void skipWhitespace() {
92885587Sobrien        while (isWhitespace(ch)) {
92985587Sobrien            nextChar();
93085587Sobrien        }
93185587Sobrien    }
93285587Sobrien
93385587Sobrien    /**
93485587Sobrien     * @param start position of first character of string
93585587Sobrien     * @param end position of character beyond last character to be included
93685587Sobrien     */
93785587Sobrien    String newString(int start, int end) {
93885587Sobrien        return new String(buf, start, end - start);
93985587Sobrien    }
94085587Sobrien
94185587Sobrien    static abstract class TagParser {
94285587Sobrien        enum Kind { INLINE, BLOCK }
943
944        final Kind kind;
945        final DCTree.Kind treeKind;
946        final boolean retainWhiteSpace;
947
948
949        TagParser(Kind k, DCTree.Kind tk) {
950            kind = k;
951            treeKind = tk;
952            retainWhiteSpace = false;
953        }
954
955        TagParser(Kind k, DCTree.Kind tk, boolean retainWhiteSpace) {
956            kind = k;
957            treeKind = tk;
958            this.retainWhiteSpace = retainWhiteSpace;
959        }
960
961        Kind getKind() {
962            return kind;
963        }
964
965        DCTree.Kind getTreeKind() {
966            return treeKind;
967        }
968
969        abstract DCTree parse(int pos) throws ParseException;
970    }
971
972    /**
973     * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
974     */
975    private void initTagParsers() {
976        TagParser[] parsers = {
977            // @author name-text
978            new TagParser(Kind.BLOCK, DCTree.Kind.AUTHOR) {
979                public DCTree parse(int pos) {
980                    List<DCTree> name = blockContent();
981                    return m.at(pos).Author(name);
982                }
983            },
984
985            // {@code text}
986            new TagParser(Kind.INLINE, DCTree.Kind.CODE, true) {
987                public DCTree parse(int pos) throws ParseException {
988                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
989                    nextChar();
990                    return m.at(pos).Code((DCText) text);
991                }
992            },
993
994            // @deprecated deprecated-text
995            new TagParser(Kind.BLOCK, DCTree.Kind.DEPRECATED) {
996                public DCTree parse(int pos) {
997                    List<DCTree> reason = blockContent();
998                    return m.at(pos).Deprecated(reason);
999                }
1000            },
1001
1002            // {@docRoot}
1003            new TagParser(Kind.INLINE, DCTree.Kind.DOC_ROOT) {
1004                public DCTree parse(int pos) throws ParseException {
1005                    if (ch == '}') {
1006                        nextChar();
1007                        return m.at(pos).DocRoot();
1008                    }
1009                    inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
1010                    nextChar();
1011                    throw new ParseException("dc.unexpected.content");
1012                }
1013            },
1014
1015            // @exception class-name description
1016            new TagParser(Kind.BLOCK, DCTree.Kind.EXCEPTION) {
1017                public DCTree parse(int pos) throws ParseException {
1018                    skipWhitespace();
1019                    DCReference ref = reference(false);
1020                    List<DCTree> description = blockContent();
1021                    return m.at(pos).Exception(ref, description);
1022                }
1023            },
1024
1025            // {@inheritDoc}
1026            new TagParser(Kind.INLINE, DCTree.Kind.INHERIT_DOC) {
1027                public DCTree parse(int pos) throws ParseException {
1028                    if (ch == '}') {
1029                        nextChar();
1030                        return m.at(pos).InheritDoc();
1031                    }
1032                    inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
1033                    nextChar();
1034                    throw new ParseException("dc.unexpected.content");
1035                }
1036            },
1037
1038            // {@link package.class#member label}
1039            new TagParser(Kind.INLINE, DCTree.Kind.LINK) {
1040                public DCTree parse(int pos) throws ParseException {
1041                    DCReference ref = reference(true);
1042                    List<DCTree> label = inlineContent();
1043                    return m.at(pos).Link(ref, label);
1044                }
1045            },
1046
1047            // {@linkplain package.class#member label}
1048            new TagParser(Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
1049                public DCTree parse(int pos) throws ParseException {
1050                    DCReference ref = reference(true);
1051                    List<DCTree> label = inlineContent();
1052                    return m.at(pos).LinkPlain(ref, label);
1053                }
1054            },
1055
1056            // {@literal text}
1057            new TagParser(Kind.INLINE, DCTree.Kind.LITERAL, true) {
1058                public DCTree parse(int pos) throws ParseException {
1059                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
1060                    nextChar();
1061                    return m.at(pos).Literal((DCText) text);
1062                }
1063            },
1064
1065            // @param parameter-name description
1066            new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) {
1067                public DCTree parse(int pos) throws ParseException {
1068                    skipWhitespace();
1069
1070                    boolean typaram = false;
1071                    if (ch == '<') {
1072                        typaram = true;
1073                        nextChar();
1074                    }
1075
1076                    DCIdentifier id = identifier();
1077
1078                    if (typaram) {
1079                        if (ch != '>')
1080                            throw new ParseException("dc.gt.expected");
1081                        nextChar();
1082                    }
1083
1084                    skipWhitespace();
1085                    List<DCTree> desc = blockContent();
1086                    return m.at(pos).Param(typaram, id, desc);
1087                }
1088            },
1089
1090            // @return description
1091            new TagParser(Kind.BLOCK, DCTree.Kind.RETURN) {
1092                public DCTree parse(int pos) {
1093                    List<DCTree> description = blockContent();
1094                    return m.at(pos).Return(description);
1095                }
1096            },
1097
1098            // @see reference | quoted-string | HTML
1099            new TagParser(Kind.BLOCK, DCTree.Kind.SEE) {
1100                public DCTree parse(int pos) throws ParseException {
1101                    skipWhitespace();
1102                    switch (ch) {
1103                        case '"':
1104                            DCText string = quotedString();
1105                            if (string != null) {
1106                                skipWhitespace();
1107                                if (ch == '@'
1108                                        || ch == EOI && bp == buf.length - 1) {
1109                                    return m.at(pos).See(List.<DCTree>of(string));
1110                                }
1111                            }
1112                            break;
1113
1114                        case '<':
1115                            List<DCTree> html = blockContent();
1116                            if (html != null)
1117                                return m.at(pos).See(html);
1118                            break;
1119
1120                        case '@':
1121                            if (newline)
1122                                throw new ParseException("dc.no.content");
1123                            break;
1124
1125                        case EOI:
1126                            if (bp == buf.length - 1)
1127                                throw new ParseException("dc.no.content");
1128                            break;
1129
1130                        default:
1131                            if (isJavaIdentifierStart(ch) || ch == '#') {
1132                                DCReference ref = reference(true);
1133                                List<DCTree> description = blockContent();
1134                                return m.at(pos).See(description.prepend(ref));
1135                            }
1136                    }
1137                    throw new ParseException("dc.unexpected.content");
1138                }
1139            },
1140
1141            // @serialData data-description
1142            new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_DATA) {
1143                public DCTree parse(int pos) {
1144                    List<DCTree> description = blockContent();
1145                    return m.at(pos).SerialData(description);
1146                }
1147            },
1148
1149            // @serialField field-name field-type description
1150            new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_FIELD) {
1151                public DCTree parse(int pos) throws ParseException {
1152                    skipWhitespace();
1153                    DCIdentifier name = identifier();
1154                    skipWhitespace();
1155                    DCReference type = reference(false);
1156                    List<DCTree> description = null;
1157                    if (isWhitespace(ch)) {
1158                        skipWhitespace();
1159                        description = blockContent();
1160                    }
1161                    return m.at(pos).SerialField(name, type, description);
1162                }
1163            },
1164
1165            // @serial field-description | include | exclude
1166            new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL) {
1167                public DCTree parse(int pos) {
1168                    List<DCTree> description = blockContent();
1169                    return m.at(pos).Serial(description);
1170                }
1171            },
1172
1173            // @since since-text
1174            new TagParser(Kind.BLOCK, DCTree.Kind.SINCE) {
1175                public DCTree parse(int pos) {
1176                    List<DCTree> description = blockContent();
1177                    return m.at(pos).Since(description);
1178                }
1179            },
1180
1181            // @throws class-name description
1182            new TagParser(Kind.BLOCK, DCTree.Kind.THROWS) {
1183                public DCTree parse(int pos) throws ParseException {
1184                    skipWhitespace();
1185                    DCReference ref = reference(false);
1186                    List<DCTree> description = blockContent();
1187                    return m.at(pos).Throws(ref, description);
1188                }
1189            },
1190
1191            // {@value package.class#field}
1192            new TagParser(Kind.INLINE, DCTree.Kind.VALUE) {
1193                public DCTree parse(int pos) throws ParseException {
1194                    DCReference ref = reference(true);
1195                    skipWhitespace();
1196                    if (ch == '}') {
1197                        nextChar();
1198                        return m.at(pos).Value(ref);
1199                    }
1200                    nextChar();
1201                    throw new ParseException("dc.unexpected.content");
1202                }
1203            },
1204
1205            // @version version-text
1206            new TagParser(Kind.BLOCK, DCTree.Kind.VERSION) {
1207                public DCTree parse(int pos) {
1208                    List<DCTree> description = blockContent();
1209                    return m.at(pos).Version(description);
1210                }
1211            },
1212        };
1213
1214        tagParsers = new HashMap<>();
1215        for (TagParser p: parsers)
1216            tagParsers.put(names.fromString(p.getTreeKind().tagName), p);
1217
1218    }
1219}
1220