aoqi@0: /* aoqi@0: * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.tools.javac.parser; aoqi@0: aoqi@0: import java.text.BreakIterator; aoqi@0: import java.util.Arrays; aoqi@0: import java.util.HashMap; aoqi@0: import java.util.HashSet; aoqi@0: import java.util.Locale; aoqi@0: import java.util.Map; aoqi@0: import java.util.Set; aoqi@0: aoqi@0: import com.sun.source.doctree.AttributeTree.ValueKind; aoqi@0: import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind; aoqi@0: import com.sun.tools.javac.parser.Tokens.Comment; aoqi@0: import com.sun.tools.javac.parser.Tokens.TokenKind; aoqi@0: import com.sun.tools.javac.tree.DCTree; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCAttribute; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCDocComment; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCEndElement; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCEndPosTree; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCErroneous; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCIdentifier; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCReference; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCStartElement; aoqi@0: import com.sun.tools.javac.tree.DCTree.DCText; aoqi@0: import com.sun.tools.javac.tree.DocTreeMaker; aoqi@0: import com.sun.tools.javac.tree.JCTree; aoqi@0: import com.sun.tools.javac.util.DiagnosticSource; aoqi@0: import com.sun.tools.javac.util.List; aoqi@0: import com.sun.tools.javac.util.ListBuffer; aoqi@0: import com.sun.tools.javac.util.Log; aoqi@0: import com.sun.tools.javac.util.Name; aoqi@0: import com.sun.tools.javac.util.Names; aoqi@0: import com.sun.tools.javac.util.Options; aoqi@0: import com.sun.tools.javac.util.Position; aoqi@0: import com.sun.tools.javac.util.StringUtils; aoqi@0: import static com.sun.tools.javac.util.LayoutCharacters.*; aoqi@0: aoqi@0: /** aoqi@0: * aoqi@0: *

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