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

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