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