/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.parser; import java.text.BreakIterator; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import com.sun.source.doctree.AttributeTree.ValueKind; import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind; import com.sun.tools.javac.parser.Tokens.Comment; import com.sun.tools.javac.parser.Tokens.TokenKind; import com.sun.tools.javac.tree.DCTree; import com.sun.tools.javac.tree.DCTree.DCAttribute; import com.sun.tools.javac.tree.DCTree.DCDocComment; import com.sun.tools.javac.tree.DCTree.DCEndElement; import com.sun.tools.javac.tree.DCTree.DCEndPosTree; import com.sun.tools.javac.tree.DCTree.DCErroneous; import com.sun.tools.javac.tree.DCTree.DCIdentifier; import com.sun.tools.javac.tree.DCTree.DCReference; import com.sun.tools.javac.tree.DCTree.DCStartElement; import com.sun.tools.javac.tree.DCTree.DCText; import com.sun.tools.javac.tree.DocTreeMaker; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.DiagnosticSource; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; import com.sun.tools.javac.util.Position; import com.sun.tools.javac.util.StringUtils; import static com.sun.tools.javac.util.LayoutCharacters.*; /** * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class DocCommentParser { static class ParseException extends Exception { private static final long serialVersionUID = 0; ParseException(String key) { super(key); } } final ParserFactory fac; final DiagnosticSource diagSource; final Comment comment; final DocTreeMaker m; final Names names; BreakIterator sentenceBreaker; /** The input buffer, index of most recent character read, * index of one past last character in buffer. */ protected char[] buf; protected int bp; protected int buflen; /** The current character. */ protected char ch; int textStart = -1; int lastNonWhite = -1; boolean newline = true; Map<Name, TagParser> tagParsers; DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) { this.fac = fac; this.diagSource = diagSource; this.comment = comment; names = fac.names; m = fac.docTreeMaker; Locale locale = (fac.locale == null) ? Locale.getDefault() : fac.locale; Options options = fac.options; boolean useBreakIterator = options.isSet("breakIterator"); if (useBreakIterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) sentenceBreaker = BreakIterator.getSentenceInstance(locale); initTagParsers(); } DCDocComment parse() { String c = comment.getText(); buf = new char[c.length() + 1]; c.getChars(0, c.length(), buf, 0); buf[buf.length - 1] = EOI; buflen = buf.length - 1; bp = -1; nextChar(); List<DCTree> body = blockContent(); List<DCTree> tags = blockTags(); // split body into first sentence and body ListBuffer<DCTree> fs = new ListBuffer<DCTree>(); loop: for (; body.nonEmpty(); body = body.tail) { DCTree t = body.head; switch (t.getKind()) { case TEXT: String s = ((DCText) t).getBody(); int i = getSentenceBreak(s); if (i > 0) { int i0 = i; while (i0 > 0 && isWhitespace(s.charAt(i0 - 1))) i0--; fs.add(m.at(t.pos).Text(s.substring(0, i0))); int i1 = i; while (i1 < s.length() && isWhitespace(s.charAt(i1))) i1++; body = body.tail; if (i1 < s.length()) body = body.prepend(m.at(t.pos + i1).Text(s.substring(i1))); break loop; } else if (body.tail.nonEmpty()) { if (isSentenceBreak(body.tail.head)) { int i0 = s.length() - 1; while (i0 > 0 && isWhitespace(s.charAt(i0))) i0--; fs.add(m.at(t.pos).Text(s.substring(0, i0 + 1))); body = body.tail; break loop; } } break; case START_ELEMENT: case END_ELEMENT: if (isSentenceBreak(t)) break loop; break; } fs.add(t); } @SuppressWarnings("unchecked") DCTree first = getFirst(fs.toList(), body, tags); int pos = (first == null) ? Position.NOPOS : first.pos; DCDocComment dc = m.at(pos).DocComment(comment, fs.toList(), body, tags); return dc; } void nextChar() { ch = buf[bp < buflen ? ++bp : buflen]; switch (ch) { case '\f': case '\n': case '\r': newline = true; } } /** * Read block content, consisting of text, html and inline tags. * Terminated by the end of input, or the beginning of the next block tag: * i.e. @ as the first non-whitespace character on a line. */ @SuppressWarnings("fallthrough") protected List<DCTree> blockContent() { ListBuffer<DCTree> trees = new ListBuffer<DCTree>(); textStart = -1; loop: while (bp < buflen) { switch (ch) { case '\n': case '\r': case '\f': newline = true; // fallthrough case ' ': case '\t': nextChar(); break; case '&': entity(trees); break; case '<': newline = false; addPendingText(trees, bp - 1); trees.add(html()); if (textStart == -1) { textStart = bp; lastNonWhite = -1; } break; case '>': newline = false; addPendingText(trees, bp - 1); trees.add(m.at(bp).Erroneous(newString(bp, bp+1), diagSource, "dc.bad.gt")); nextChar(); if (textStart == -1) { textStart = bp; lastNonWhite = -1; } break; case '{': inlineTag(trees); break; case '@': if (newline) { addPendingText(trees, lastNonWhite); break loop; } // fallthrough default: newline = false; if (textStart == -1) textStart = bp; lastNonWhite = bp; nextChar(); } } if (lastNonWhite != -1) addPendingText(trees, lastNonWhite); return trees.toList(); } /** * Read a series of block tags, including their content. * Standard tags parse their content appropriately. * Non-standard tags are represented by {@link UnknownBlockTag}. */ protected List<DCTree> blockTags() { ListBuffer<DCTree> tags = new ListBuffer<DCTree>(); while (ch == '@') tags.add(blockTag()); return tags.toList(); } /** * Read a single block tag, including its content. * Standard tags parse their content appropriately. * Non-standard tags are represented by {@link UnknownBlockTag}. */ protected DCTree blockTag() { int p = bp; try { nextChar(); if (isIdentifierStart(ch)) { Name name = readTagName(); TagParser tp = tagParsers.get(name); if (tp == null) { List<DCTree> content = blockContent(); return m.at(p).UnknownBlockTag(name, content); } else { switch (tp.getKind()) { case BLOCK: return tp.parse(p); case INLINE: return erroneous("dc.bad.inline.tag", p); } } } blockContent(); return erroneous("dc.no.tag.name", p); } catch (ParseException e) { blockContent(); return erroneous(e.getMessage(), p); } } protected void inlineTag(ListBuffer<DCTree> list) { newline = false; nextChar(); if (ch == '@') { addPendingText(list, bp - 2); list.add(inlineTag()); textStart = bp; lastNonWhite = -1; } else { if (textStart == -1) textStart = bp - 1; lastNonWhite = bp; } } /** * Read a single inline tag, including its content. * Standard tags parse their content appropriately. * Non-standard tags are represented by {@link UnknownBlockTag}. * Malformed tags may be returned as {@link Erroneous}. */ protected DCTree inlineTag() { int p = bp - 1; try { nextChar(); if (isIdentifierStart(ch)) { Name name = readTagName(); skipWhitespace(); TagParser tp = tagParsers.get(name); if (tp == null) { DCTree text = inlineText(); if (text != null) { nextChar(); return m.at(p).UnknownInlineTag(name, List.of(text)).setEndPos(bp); } } else if (tp.getKind() == TagParser.Kind.INLINE) { DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p); if (tree != null) { return tree.setEndPos(bp); } } else { inlineText(); // skip content nextChar(); } } return erroneous("dc.no.tag.name", p); } catch (ParseException e) { return erroneous(e.getMessage(), p); } } /** * Read plain text content of an inline tag. * Matching pairs of { } are skipped; the text is terminated by the first * unmatched }. It is an error if the beginning of the next tag is detected. */ protected DCTree inlineText() throws ParseException { skipWhitespace(); int pos = bp; int depth = 1; loop: while (bp < buflen) { switch (ch) { case '\n': case '\r': case '\f': newline = true; break; case ' ': case '\t': break; case '{': newline = false; lastNonWhite = bp; depth++; break; case '}': if (--depth == 0) { return m.at(pos).Text(newString(pos, bp)); } newline = false; lastNonWhite = bp; break; case '@': if (newline) break loop; newline = false; lastNonWhite = bp; break; default: newline = false; lastNonWhite = bp; break; } nextChar(); } throw new ParseException("dc.unterminated.inline.tag"); } /** * Read Java class name, possibly followed by member * Matching pairs of < > are skipped. The text is terminated by the first * unmatched }. It is an error if the beginning of the next tag is detected. */ // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE // TODO: improve quality of parse to forbid bad constructions. @SuppressWarnings("fallthrough") protected DCReference reference(boolean allowMember) throws ParseException { int pos = bp; int depth = 0; // scan to find the end of the signature, by looking for the first // whitespace not enclosed in () or <>, or the end of the tag loop: while (bp < buflen) { switch (ch) { case '\n': case '\r': case '\f': newline = true; // fallthrough case ' ': case '\t': if (depth == 0) break loop; break; case '(': case '<': newline = false; depth++; break; case ')': case '>': newline = false; --depth; break; case '}': if (bp == pos) return null; newline = false; break loop; case '@': if (newline) break loop; // fallthrough default: newline = false; } nextChar(); } if (depth != 0) throw new ParseException("dc.unterminated.signature"); String sig = newString(pos, bp); // Break sig apart into qualifiedExpr member paramTypes. JCTree qualExpr; Name member; List<JCTree> paramTypes; Log.DeferredDiagnosticHandler deferredDiagnosticHandler = new Log.DeferredDiagnosticHandler(fac.log); try { int hash = sig.indexOf("#"); int lparen = sig.indexOf("(", hash + 1); if (hash == -1) { if (lparen == -1) { qualExpr = parseType(sig); member = null; } else { qualExpr = null; member = parseMember(sig.substring(0, lparen)); } } else { qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash)); if (lparen == -1) member = parseMember(sig.substring(hash + 1)); else member = parseMember(sig.substring(hash + 1, lparen)); } if (lparen < 0) { paramTypes = null; } else { int rparen = sig.indexOf(")", lparen); if (rparen != sig.length() - 1) throw new ParseException("dc.ref.bad.parens"); paramTypes = parseParams(sig.substring(lparen + 1, rparen)); } if (!deferredDiagnosticHandler.getDiagnostics().isEmpty()) throw new ParseException("dc.ref.syntax.error"); } finally { fac.log.popDiagnosticHandler(deferredDiagnosticHandler); } return m.at(pos).Reference(sig, qualExpr, member, paramTypes).setEndPos(bp); } JCTree parseType(String s) throws ParseException { JavacParser p = fac.newParser(s, false, false, false); JCTree tree = p.parseType(); if (p.token().kind != TokenKind.EOF) throw new ParseException("dc.ref.unexpected.input"); return tree; } Name parseMember(String s) throws ParseException { JavacParser p = fac.newParser(s, false, false, false); Name name = p.ident(); if (p.token().kind != TokenKind.EOF) throw new ParseException("dc.ref.unexpected.input"); return name; } List<JCTree> parseParams(String s) throws ParseException { if (s.trim().isEmpty()) return List.nil(); JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false); ListBuffer<JCTree> paramTypes = new ListBuffer<JCTree>(); paramTypes.add(p.parseType()); if (p.token().kind == TokenKind.IDENTIFIER) p.nextToken(); while (p.token().kind == TokenKind.COMMA) { p.nextToken(); paramTypes.add(p.parseType()); if (p.token().kind == TokenKind.IDENTIFIER) p.nextToken(); } if (p.token().kind != TokenKind.EOF) throw new ParseException("dc.ref.unexpected.input"); return paramTypes.toList(); } /** * Read Java identifier * Matching pairs of { } are skipped; the text is terminated by the first * unmatched }. It is an error if the beginning of the next tag is detected. */ @SuppressWarnings("fallthrough") protected DCIdentifier identifier() throws ParseException { skipWhitespace(); int pos = bp; if (isJavaIdentifierStart(ch)) { Name name = readJavaIdentifier(); return m.at(pos).Identifier(name); } throw new ParseException("dc.identifier.expected"); } /** * Read a quoted string. * It is an error if the beginning of the next tag is detected. */ @SuppressWarnings("fallthrough") protected DCText quotedString() { int pos = bp; nextChar(); loop: while (bp < buflen) { switch (ch) { case '\n': case '\r': case '\f': newline = true; break; case ' ': case '\t': break; case '"': nextChar(); // trim trailing white-space? return m.at(pos).Text(newString(pos, bp)); case '@': if (newline) break loop; } nextChar(); } return null; } /** * Read general text content of an inline tag, including HTML entities and elements. * Matching pairs of { } are skipped; the text is terminated by the first * unmatched }. It is an error if the beginning of the next tag is detected. */ @SuppressWarnings("fallthrough") protected List<DCTree> inlineContent() { ListBuffer<DCTree> trees = new ListBuffer<DCTree>(); skipWhitespace(); int pos = bp; int depth = 1; textStart = -1; loop: while (bp < buflen) { switch (ch) { case '\n': case '\r': case '\f': newline = true; // fall through case ' ': case '\t': nextChar(); break; case '&': entity(trees); break; case '<': newline = false; addPendingText(trees, bp - 1); trees.add(html()); break; case '{': newline = false; depth++; nextChar(); break; case '}': newline = false; if (--depth == 0) { addPendingText(trees, bp - 1); nextChar(); return trees.toList(); } nextChar(); break; case '@': if (newline) break loop; // fallthrough default: if (textStart == -1) textStart = bp; nextChar(); break; } } return List.<DCTree>of(erroneous("dc.unterminated.inline.tag", pos)); } protected void entity(ListBuffer<DCTree> list) { newline = false; addPendingText(list, bp - 1); list.add(entity()); if (textStart == -1) { textStart = bp; lastNonWhite = -1; } } /** * Read an HTML entity. * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; } */ protected DCTree entity() { int p = bp; nextChar(); Name name = null; boolean checkSemi = false; if (ch == '#') { int namep = bp; nextChar(); if (isDecimalDigit(ch)) { nextChar(); while (isDecimalDigit(ch)) nextChar(); name = names.fromChars(buf, namep, bp - namep); } else if (ch == 'x' || ch == 'X') { nextChar(); if (isHexDigit(ch)) { nextChar(); while (isHexDigit(ch)) nextChar(); name = names.fromChars(buf, namep, bp - namep); } } } else if (isIdentifierStart(ch)) { name = readIdentifier(); } if (name == null) return erroneous("dc.bad.entity", p); else { if (ch != ';') return erroneous("dc.missing.semicolon", p); nextChar(); return m.at(p).Entity(name); } } /** * Read the start or end of an HTML tag, or an HTML comment * {@literal <identifier attrs> } or {@literal </identifier> } */ protected DCTree html() { int p = bp; nextChar(); if (isIdentifierStart(ch)) { Name name = readIdentifier(); List<DCTree> attrs = htmlAttrs(); if (attrs != null) { boolean selfClosing = false; if (ch == '/') { nextChar(); selfClosing = true; } if (ch == '>') { nextChar(); return m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp); } } } else if (ch == '/') { nextChar(); if (isIdentifierStart(ch)) { Name name = readIdentifier(); skipWhitespace(); if (ch == '>') { nextChar(); return m.at(p).EndElement(name); } } } else if (ch == '!') { nextChar(); if (ch == '-') { nextChar(); if (ch == '-') { nextChar(); while (bp < buflen) { int dash = 0; while (ch == '-') { dash++; nextChar(); } // strictly speaking, a comment should not contain "--" // so dash > 2 is an error, dash == 2 implies ch == '>' if (dash >= 2 && ch == '>') { nextChar(); return m.at(p).Comment(newString(p, bp)); } nextChar(); } } } } bp = p + 1; ch = buf[bp]; return erroneous("dc.malformed.html", p); } /** * Read a series of HTML attributes, terminated by {@literal > }. * Each attribute is of the form {@literal identifier[=value] }. * "value" may be unquoted, single-quoted, or double-quoted. */ protected List<DCTree> htmlAttrs() { ListBuffer<DCTree> attrs = new ListBuffer<DCTree>(); skipWhitespace(); loop: while (isIdentifierStart(ch)) { int namePos = bp; Name name = readIdentifier(); skipWhitespace(); List<DCTree> value = null; ValueKind vkind = ValueKind.EMPTY; if (ch == '=') { ListBuffer<DCTree> v = new ListBuffer<DCTree>(); nextChar(); skipWhitespace(); if (ch == '\'' || ch == '"') { vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE; char quote = ch; nextChar(); textStart = bp; while (bp < buflen && ch != quote) { if (newline && ch == '@') { attrs.add(erroneous("dc.unterminated.string", namePos)); // No point trying to read more. // In fact, all attrs get discarded by the caller // and superseded by a malformed.html node because // the html tag itself is not terminated correctly. break loop; } attrValueChar(v); } addPendingText(v, bp - 1); nextChar(); } else { vkind = ValueKind.UNQUOTED; textStart = bp; while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) { attrValueChar(v); } addPendingText(v, bp - 1); } skipWhitespace(); value = v.toList(); } DCAttribute attr = m.at(namePos).Attribute(name, vkind, value); attrs.add(attr); } return attrs.toList(); } protected void attrValueChar(ListBuffer<DCTree> list) { switch (ch) { case '&': entity(list); break; case '{': inlineTag(list); break; default: nextChar(); } } protected void addPendingText(ListBuffer<DCTree> list, int textEnd) { if (textStart != -1) { if (textStart <= textEnd) { list.add(m.at(textStart).Text(newString(textStart, textEnd + 1))); } textStart = -1; } } protected DCErroneous erroneous(String code, int pos) { int i = bp - 1; loop: while (i > pos) { switch (buf[i]) { case '\f': case '\n': case '\r': newline = true; break; case '\t': case ' ': break; default: break loop; } i--; } textStart = -1; return m.at(pos).Erroneous(newString(pos, i + 1), diagSource, code); } @SuppressWarnings("unchecked") <T> T getFirst(List<T>... lists) { for (List<T> list: lists) { if (list.nonEmpty()) return list.head; } return null; } protected boolean isIdentifierStart(char ch) { return Character.isUnicodeIdentifierStart(ch); } protected Name readIdentifier() { int start = bp; nextChar(); while (bp < buflen && Character.isUnicodeIdentifierPart(ch)) nextChar(); return names.fromChars(buf, start, bp - start); } protected Name readTagName() { int start = bp; nextChar(); while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '.')) nextChar(); return names.fromChars(buf, start, bp - start); } protected boolean isJavaIdentifierStart(char ch) { return Character.isJavaIdentifierStart(ch); } protected Name readJavaIdentifier() { int start = bp; nextChar(); while (bp < buflen && Character.isJavaIdentifierPart(ch)) nextChar(); return names.fromChars(buf, start, bp - start); } protected boolean isDecimalDigit(char ch) { return ('0' <= ch && ch <= '9'); } protected boolean isHexDigit(char ch) { return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'); } protected boolean isUnquotedAttrValueTerminator(char ch) { switch (ch) { case '\f': case '\n': case '\r': case '\t': case ' ': case '"': case '\'': case '`': case '=': case '<': case '>': return true; default: return false; } } protected boolean isWhitespace(char ch) { return Character.isWhitespace(ch); } protected void skipWhitespace() { while (isWhitespace(ch)) nextChar(); } protected int getSentenceBreak(String s) { if (sentenceBreaker != null) { sentenceBreaker.setText(s); int i = sentenceBreaker.next(); return (i == s.length()) ? -1 : i; } // scan for period followed by whitespace boolean period = false; for (int i = 0; i < s.length(); i++) { switch (s.charAt(i)) { case '.': period = true; break; case ' ': case '\f': case '\n': case '\r': case '\t': if (period) return i; break; default: period = false; break; } } return -1; } Set<String> htmlBlockTags = new HashSet<String>(Arrays.asList( "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre")); protected boolean isSentenceBreak(Name n) { return htmlBlockTags.contains(StringUtils.toLowerCase(n.toString())); } protected boolean isSentenceBreak(DCTree t) { switch (t.getKind()) { case START_ELEMENT: return isSentenceBreak(((DCStartElement) t).getName()); case END_ELEMENT: return isSentenceBreak(((DCEndElement) t).getName()); } return false; } /** * @param start position of first character of string * @param end position of character beyond last character to be included */ String newString(int start, int end) { return new String(buf, start, end - start); } static abstract class TagParser { enum Kind { INLINE, BLOCK } Kind kind; DCTree.Kind treeKind; TagParser(Kind k, DCTree.Kind tk) { kind = k; treeKind = tk; } Kind getKind() { return kind; } DCTree.Kind getTreeKind() { return treeKind; } abstract DCTree parse(int pos) throws ParseException; } /** * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a> */ private void initTagParsers() { TagParser[] parsers = { // @author name-text new TagParser(Kind.BLOCK, DCTree.Kind.AUTHOR) { public DCTree parse(int pos) { List<DCTree> name = blockContent(); return m.at(pos).Author(name); } }, // {@code text} new TagParser(Kind.INLINE, DCTree.Kind.CODE) { public DCTree parse(int pos) throws ParseException { DCTree text = inlineText(); nextChar(); return m.at(pos).Code((DCText) text); } }, // @deprecated deprecated-text new TagParser(Kind.BLOCK, DCTree.Kind.DEPRECATED) { public DCTree parse(int pos) { List<DCTree> reason = blockContent(); return m.at(pos).Deprecated(reason); } }, // {@docRoot} new TagParser(Kind.INLINE, DCTree.Kind.DOC_ROOT) { public DCTree parse(int pos) throws ParseException { if (ch == '}') { nextChar(); return m.at(pos).DocRoot(); } inlineText(); // skip unexpected content nextChar(); throw new ParseException("dc.unexpected.content"); } }, // @exception class-name description new TagParser(Kind.BLOCK, DCTree.Kind.EXCEPTION) { public DCTree parse(int pos) throws ParseException { skipWhitespace(); DCReference ref = reference(false); List<DCTree> description = blockContent(); return m.at(pos).Exception(ref, description); } }, // {@inheritDoc} new TagParser(Kind.INLINE, DCTree.Kind.INHERIT_DOC) { public DCTree parse(int pos) throws ParseException { if (ch == '}') { nextChar(); return m.at(pos).InheritDoc(); } inlineText(); // skip unexpected content nextChar(); throw new ParseException("dc.unexpected.content"); } }, // {@link package.class#member label} new TagParser(Kind.INLINE, DCTree.Kind.LINK) { public DCTree parse(int pos) throws ParseException { DCReference ref = reference(true); List<DCTree> label = inlineContent(); return m.at(pos).Link(ref, label); } }, // {@linkplain package.class#member label} new TagParser(Kind.INLINE, DCTree.Kind.LINK_PLAIN) { public DCTree parse(int pos) throws ParseException { DCReference ref = reference(true); List<DCTree> label = inlineContent(); return m.at(pos).LinkPlain(ref, label); } }, // {@literal text} new TagParser(Kind.INLINE, DCTree.Kind.LITERAL) { public DCTree parse(int pos) throws ParseException { DCTree text = inlineText(); nextChar(); return m.at(pos).Literal((DCText) text); } }, // @param parameter-name description new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) { public DCTree parse(int pos) throws ParseException { skipWhitespace(); boolean typaram = false; if (ch == '<') { typaram = true; nextChar(); } DCIdentifier id = identifier(); if (typaram) { if (ch != '>') throw new ParseException("dc.gt.expected"); nextChar(); } skipWhitespace(); List<DCTree> desc = blockContent(); return m.at(pos).Param(typaram, id, desc); } }, // @return description new TagParser(Kind.BLOCK, DCTree.Kind.RETURN) { public DCTree parse(int pos) { List<DCTree> description = blockContent(); return m.at(pos).Return(description); } }, // @see reference | quoted-string | HTML new TagParser(Kind.BLOCK, DCTree.Kind.SEE) { public DCTree parse(int pos) throws ParseException { skipWhitespace(); switch (ch) { case '"': DCText string = quotedString(); if (string != null) { skipWhitespace(); if (ch == '@' || ch == EOI && bp == buf.length - 1) { return m.at(pos).See(List.<DCTree>of(string)); } } break; case '<': List<DCTree> html = blockContent(); if (html != null) return m.at(pos).See(html); break; case '@': if (newline) throw new ParseException("dc.no.content"); break; case EOI: if (bp == buf.length - 1) throw new ParseException("dc.no.content"); break; default: if (isJavaIdentifierStart(ch) || ch == '#') { DCReference ref = reference(true); List<DCTree> description = blockContent(); return m.at(pos).See(description.prepend(ref)); } } throw new ParseException("dc.unexpected.content"); } }, // @serialData data-description new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_DATA) { public DCTree parse(int pos) { List<DCTree> description = blockContent(); return m.at(pos).SerialData(description); } }, // @serialField field-name field-type description new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_FIELD) { public DCTree parse(int pos) throws ParseException { skipWhitespace(); DCIdentifier name = identifier(); skipWhitespace(); DCReference type = reference(false); List<DCTree> description = null; if (isWhitespace(ch)) { skipWhitespace(); description = blockContent(); } return m.at(pos).SerialField(name, type, description); } }, // @serial field-description | include | exclude new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL) { public DCTree parse(int pos) { List<DCTree> description = blockContent(); return m.at(pos).Serial(description); } }, // @since since-text new TagParser(Kind.BLOCK, DCTree.Kind.SINCE) { public DCTree parse(int pos) { List<DCTree> description = blockContent(); return m.at(pos).Since(description); } }, // @throws class-name description new TagParser(Kind.BLOCK, DCTree.Kind.THROWS) { public DCTree parse(int pos) throws ParseException { skipWhitespace(); DCReference ref = reference(false); List<DCTree> description = blockContent(); return m.at(pos).Throws(ref, description); } }, // {@value package.class#field} new TagParser(Kind.INLINE, DCTree.Kind.VALUE) { public DCTree parse(int pos) throws ParseException { DCReference ref = reference(true); skipWhitespace(); if (ch == '}') { nextChar(); return m.at(pos).Value(ref); } nextChar(); throw new ParseException("dc.unexpected.content"); } }, // @version version-text new TagParser(Kind.BLOCK, DCTree.Kind.VERSION) { public DCTree parse(int pos) { List<DCTree> description = blockContent(); return m.at(pos).Version(description); } }, }; tagParsers = new HashMap<Name,TagParser>(); for (TagParser p: parsers) tagParsers.put(names.fromString(p.getTreeKind().tagName), p); } }