--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Wed Nov 14 17:23:10 2012 -0800
@@ -0,0 +1,1288 @@
+/*
+ * Copyright (c) 2012, 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 com.sun.tools.javac.util.Filter;
+import java.text.BreakIterator;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Queue;
+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.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.JCDiagnostic;
+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 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)) {
+ int namePos = bp;
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ int nameLen = bp - namePos;
+
+ Name name = names.fromChars(buf, namePos, nameLen);
+ 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)) {
+ int namePos = bp;
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ int nameLen = bp - namePos;
+ skipWhitespace();
+
+ Name name = names.fromChars(buf, namePos, nameLen);
+ TagParser tp = tagParsers.get(name);
+ if (tp == null) {
+ DCTree text = inlineText();
+ if (text != null) {
+ nextChar();
+ return m.at(p).UnknownInlineTag(name, List.of(text));
+ }
+ } else if (tp.getKind() == TagParser.Kind.INLINE) {
+ DCTree tree = tp.parse(p);
+ if (tree != null) {
+ return tree;
+ }
+ } 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);
+ }
+
+ 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)) {
+ nextChar();
+ while (isJavaIdentifierPart(ch))
+ nextChar();
+ return m.at(pos).Identifier(names.fromChars(buf, pos, bp - pos));
+ }
+
+ 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();
+ int namep = bp;
+ boolean checkSemi = false;
+ if (ch == '#') {
+ nextChar();
+ if (isDecimalDigit(ch)) {
+ nextChar();
+ while (isDecimalDigit(ch))
+ nextChar();
+ checkSemi = true;
+ } else if (ch == 'x' || ch == 'X') {
+ nextChar();
+ if (isHexDigit(ch)) {
+ nextChar();
+ while (isHexDigit(ch))
+ nextChar();
+ checkSemi = true;
+ }
+ }
+ } else if (isIdentifierStart(ch)) {
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ checkSemi = true;
+ }
+
+ if (checkSemi && ch == ';') {
+ nextChar();
+ return m.at(p).Entity(names.fromChars(buf, namep, bp - namep - 1));
+ } else {
+ String code = checkSemi ? "dc.missing.semicolon" : "dc.bad.entity";
+ return erroneous(code, p);
+ }
+ }
+
+ /**
+ * 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)) {
+ int namePos = bp;
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ int nameLen = bp - namePos;
+ List<DCTree> attrs = htmlAttrs();
+ if (attrs != null) {
+ boolean selfClosing = false;
+ if (ch == '/') {
+ nextChar();
+ selfClosing = true;
+ }
+ if (ch == '>') {
+ nextChar();
+ Name name = names.fromChars(buf, namePos, nameLen);
+ return m.at(p).StartElement(name, attrs, selfClosing);
+ }
+ }
+ } else if (ch == '/') {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ int namePos = bp;
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ int nameLen = bp - namePos;
+ skipWhitespace();
+ if (ch == '>') {
+ nextChar();
+ Name name = names.fromChars(buf, namePos, nameLen);
+ 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;
+ nextChar();
+ while (isIdentifierPart(ch))
+ nextChar();
+ int nameLen = bp - namePos;
+ 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();
+ }
+ Name name = names.fromChars(buf, namePos, nameLen);
+ 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 && 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 > 0) {
+ 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 boolean isIdentifierPart(char ch) {
+ return Character.isUnicodeIdentifierPart(ch);
+ }
+
+ protected boolean isJavaIdentifierStart(char ch) {
+ return Character.isJavaIdentifierStart(ch);
+ }
+
+ protected boolean isJavaIdentifierPart(char ch) {
+ return Character.isJavaIdentifierPart(ch);
+ }
+
+ 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(n.toString().toLowerCase());
+ }
+
+ 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="http://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 == '@')
+ 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;
+
+ 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);
+
+ }
+}