Fri, 30 Nov 2012 15:14:36 +0000
8004101: Add checks for method reference well-formedness
Summary: Bring method reference type-checking in sync with latest EDR
Reviewed-by: jjg
1 /*
2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.javac.parser;
28 import com.sun.tools.javac.util.Filter;
29 import java.text.BreakIterator;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedList;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.Queue;
37 import java.util.Set;
39 import com.sun.source.doctree.AttributeTree.ValueKind;
40 import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
41 import com.sun.tools.javac.parser.Tokens.Comment;
42 import com.sun.tools.javac.parser.Tokens.TokenKind;
43 import com.sun.tools.javac.tree.DCTree;
44 import com.sun.tools.javac.tree.DCTree.DCAttribute;
45 import com.sun.tools.javac.tree.DCTree.DCDocComment;
46 import com.sun.tools.javac.tree.DCTree.DCEndElement;
47 import com.sun.tools.javac.tree.DCTree.DCErroneous;
48 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
49 import com.sun.tools.javac.tree.DCTree.DCReference;
50 import com.sun.tools.javac.tree.DCTree.DCStartElement;
51 import com.sun.tools.javac.tree.DCTree.DCText;
52 import com.sun.tools.javac.tree.DocTreeMaker;
53 import com.sun.tools.javac.tree.JCTree;
54 import com.sun.tools.javac.util.DiagnosticSource;
55 import com.sun.tools.javac.util.JCDiagnostic;
56 import com.sun.tools.javac.util.List;
57 import com.sun.tools.javac.util.ListBuffer;
58 import com.sun.tools.javac.util.Log;
59 import com.sun.tools.javac.util.Name;
60 import com.sun.tools.javac.util.Names;
61 import com.sun.tools.javac.util.Options;
62 import com.sun.tools.javac.util.Position;
63 import static com.sun.tools.javac.util.LayoutCharacters.*;
65 /**
66 *
67 * <p><b>This is NOT part of any supported API.
68 * If you write code that depends on this, you do so at your own risk.
69 * This code and its internal interfaces are subject to change or
70 * deletion without notice.</b>
71 */
72 public class DocCommentParser {
73 static class ParseException extends Exception {
74 private static final long serialVersionUID = 0;
75 ParseException(String key) {
76 super(key);
77 }
78 }
80 final ParserFactory fac;
81 final DiagnosticSource diagSource;
82 final Comment comment;
83 final DocTreeMaker m;
84 final Names names;
86 BreakIterator sentenceBreaker;
88 /** The input buffer, index of most recent character read,
89 * index of one past last character in buffer.
90 */
91 protected char[] buf;
92 protected int bp;
93 protected int buflen;
95 /** The current character.
96 */
97 protected char ch;
99 int textStart = -1;
100 int lastNonWhite = -1;
101 boolean newline = true;
103 Map<Name, TagParser> tagParsers;
105 DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
106 this.fac = fac;
107 this.diagSource = diagSource;
108 this.comment = comment;
109 names = fac.names;
110 m = fac.docTreeMaker;
112 Locale locale = (fac.locale == null) ? Locale.getDefault() : fac.locale;
114 Options options = fac.options;
115 boolean useBreakIterator = options.isSet("breakIterator");
116 if (useBreakIterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage()))
117 sentenceBreaker = BreakIterator.getSentenceInstance(locale);
119 initTagParsers();
120 }
122 DCDocComment parse() {
123 String c = comment.getText();
124 buf = new char[c.length() + 1];
125 c.getChars(0, c.length(), buf, 0);
126 buf[buf.length - 1] = EOI;
127 buflen = buf.length - 1;
128 bp = -1;
129 nextChar();
131 List<DCTree> body = blockContent();
132 List<DCTree> tags = blockTags();
134 // split body into first sentence and body
135 ListBuffer<DCTree> fs = new ListBuffer<DCTree>();
136 loop:
137 for (; body.nonEmpty(); body = body.tail) {
138 DCTree t = body.head;
139 switch (t.getKind()) {
140 case TEXT:
141 String s = ((DCText) t).getBody();
142 int i = getSentenceBreak(s);
143 if (i > 0) {
144 int i0 = i;
145 while (i0 > 0 && isWhitespace(s.charAt(i0 - 1)))
146 i0--;
147 fs.add(m.at(t.pos).Text(s.substring(0, i0)));
148 int i1 = i;
149 while (i1 < s.length() && isWhitespace(s.charAt(i1)))
150 i1++;
151 body = body.tail;
152 if (i1 < s.length())
153 body = body.prepend(m.at(t.pos + i1).Text(s.substring(i1)));
154 break loop;
155 } else if (body.tail.nonEmpty()) {
156 if (isSentenceBreak(body.tail.head)) {
157 int i0 = s.length() - 1;
158 while (i0 > 0 && isWhitespace(s.charAt(i0)))
159 i0--;
160 fs.add(m.at(t.pos).Text(s.substring(0, i0 + 1)));
161 body = body.tail;
162 break loop;
163 }
164 }
165 break;
167 case START_ELEMENT:
168 case END_ELEMENT:
169 if (isSentenceBreak(t))
170 break loop;
171 break;
172 }
173 fs.add(t);
174 }
176 @SuppressWarnings("unchecked")
177 DCTree first = getFirst(fs.toList(), body, tags);
178 int pos = (first == null) ? Position.NOPOS : first.pos;
180 DCDocComment dc = m.at(pos).DocComment(comment, fs.toList(), body, tags);
181 return dc;
182 }
184 void nextChar() {
185 ch = buf[bp < buflen ? ++bp : buflen];
186 switch (ch) {
187 case '\f': case '\n': case '\r':
188 newline = true;
189 }
190 }
192 /**
193 * Read block content, consisting of text, html and inline tags.
194 * Terminated by the end of input, or the beginning of the next block tag:
195 * i.e. @ as the first non-whitespace character on a line.
196 */
197 @SuppressWarnings("fallthrough")
198 protected List<DCTree> blockContent() {
199 ListBuffer<DCTree> trees = new ListBuffer<DCTree>();
200 textStart = -1;
202 loop:
203 while (bp < buflen) {
204 switch (ch) {
205 case '\n': case '\r': case '\f':
206 newline = true;
207 // fallthrough
209 case ' ': case '\t':
210 nextChar();
211 break;
213 case '&':
214 entity(trees);
215 break;
217 case '<':
218 newline = false;
219 addPendingText(trees, bp - 1);
220 trees.add(html());
221 if (textStart == -1) {
222 textStart = bp;
223 lastNonWhite = -1;
224 }
225 break;
227 case '>':
228 newline = false;
229 addPendingText(trees, bp - 1);
230 trees.add(m.at(bp).Erroneous(newString(bp, bp+1), diagSource, "dc.bad.gt"));
231 nextChar();
232 if (textStart == -1) {
233 textStart = bp;
234 lastNonWhite = -1;
235 }
236 break;
238 case '{':
239 inlineTag(trees);
240 break;
242 case '@':
243 if (newline) {
244 addPendingText(trees, lastNonWhite);
245 break loop;
246 }
247 // fallthrough
249 default:
250 newline = false;
251 if (textStart == -1)
252 textStart = bp;
253 lastNonWhite = bp;
254 nextChar();
255 }
256 }
258 if (lastNonWhite != -1)
259 addPendingText(trees, lastNonWhite);
261 return trees.toList();
262 }
264 /**
265 * Read a series of block tags, including their content.
266 * Standard tags parse their content appropriately.
267 * Non-standard tags are represented by {@link UnknownBlockTag}.
268 */
269 protected List<DCTree> blockTags() {
270 ListBuffer<DCTree> tags = new ListBuffer<DCTree>();
271 while (ch == '@')
272 tags.add(blockTag());
273 return tags.toList();
274 }
276 /**
277 * Read a single block tag, including its content.
278 * Standard tags parse their content appropriately.
279 * Non-standard tags are represented by {@link UnknownBlockTag}.
280 */
281 protected DCTree blockTag() {
282 int p = bp;
283 try {
284 nextChar();
285 if (isIdentifierStart(ch)) {
286 int namePos = bp;
287 nextChar();
288 while (isIdentifierPart(ch))
289 nextChar();
290 int nameLen = bp - namePos;
292 Name name = names.fromChars(buf, namePos, nameLen);
293 TagParser tp = tagParsers.get(name);
294 if (tp == null) {
295 List<DCTree> content = blockContent();
296 return m.at(p).UnknownBlockTag(name, content);
297 } else {
298 switch (tp.getKind()) {
299 case BLOCK:
300 return tp.parse(p);
301 case INLINE:
302 return erroneous("dc.bad.inline.tag", p);
303 }
304 }
305 }
306 blockContent();
308 return erroneous("dc.no.tag.name", p);
309 } catch (ParseException e) {
310 blockContent();
311 return erroneous(e.getMessage(), p);
312 }
313 }
315 protected void inlineTag(ListBuffer<DCTree> list) {
316 newline = false;
317 nextChar();
318 if (ch == '@') {
319 addPendingText(list, bp - 2);
320 list.add(inlineTag());
321 textStart = bp;
322 lastNonWhite = -1;
323 } else {
324 if (textStart == -1)
325 textStart = bp - 1;
326 lastNonWhite = bp;
327 }
328 }
330 /**
331 * Read a single inline tag, including its content.
332 * Standard tags parse their content appropriately.
333 * Non-standard tags are represented by {@link UnknownBlockTag}.
334 * Malformed tags may be returned as {@link Erroneous}.
335 */
336 protected DCTree inlineTag() {
337 int p = bp - 1;
338 try {
339 nextChar();
340 if (isIdentifierStart(ch)) {
341 int namePos = bp;
342 nextChar();
343 while (isIdentifierPart(ch))
344 nextChar();
345 int nameLen = bp - namePos;
346 skipWhitespace();
348 Name name = names.fromChars(buf, namePos, nameLen);
349 TagParser tp = tagParsers.get(name);
350 if (tp == null) {
351 DCTree text = inlineText();
352 if (text != null) {
353 nextChar();
354 return m.at(p).UnknownInlineTag(name, List.of(text));
355 }
356 } else if (tp.getKind() == TagParser.Kind.INLINE) {
357 DCTree tree = tp.parse(p);
358 if (tree != null) {
359 return tree;
360 }
361 } else {
362 inlineText(); // skip content
363 nextChar();
364 }
365 }
366 return erroneous("dc.no.tag.name", p);
367 } catch (ParseException e) {
368 return erroneous(e.getMessage(), p);
369 }
370 }
372 /**
373 * Read plain text content of an inline tag.
374 * Matching pairs of { } are skipped; the text is terminated by the first
375 * unmatched }. It is an error if the beginning of the next tag is detected.
376 */
377 protected DCTree inlineText() throws ParseException {
378 skipWhitespace();
379 int pos = bp;
380 int depth = 1;
382 loop:
383 while (bp < buflen) {
384 switch (ch) {
385 case '\n': case '\r': case '\f':
386 newline = true;
387 break;
389 case ' ': case '\t':
390 break;
392 case '{':
393 newline = false;
394 lastNonWhite = bp;
395 depth++;
396 break;
398 case '}':
399 if (--depth == 0) {
400 return m.at(pos).Text(newString(pos, bp));
401 }
402 newline = false;
403 lastNonWhite = bp;
404 break;
406 case '@':
407 if (newline)
408 break loop;
409 newline = false;
410 lastNonWhite = bp;
411 break;
413 default:
414 newline = false;
415 lastNonWhite = bp;
416 break;
417 }
418 nextChar();
419 }
420 throw new ParseException("dc.unterminated.inline.tag");
421 }
423 /**
424 * Read Java class name, possibly followed by member
425 * Matching pairs of < > are skipped. The text is terminated by the first
426 * unmatched }. It is an error if the beginning of the next tag is detected.
427 */
428 // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
429 // TODO: improve quality of parse to forbid bad constructions.
430 @SuppressWarnings("fallthrough")
431 protected DCReference reference(boolean allowMember) throws ParseException {
432 int pos = bp;
433 int depth = 0;
435 // scan to find the end of the signature, by looking for the first
436 // whitespace not enclosed in () or <>, or the end of the tag
437 loop:
438 while (bp < buflen) {
439 switch (ch) {
440 case '\n': case '\r': case '\f':
441 newline = true;
442 // fallthrough
444 case ' ': case '\t':
445 if (depth == 0)
446 break loop;
447 break;
449 case '(':
450 case '<':
451 newline = false;
452 depth++;
453 break;
455 case ')':
456 case '>':
457 newline = false;
458 --depth;
459 break;
461 case '}':
462 if (bp == pos)
463 return null;
464 newline = false;
465 break loop;
467 case '@':
468 if (newline)
469 break loop;
470 // fallthrough
472 default:
473 newline = false;
475 }
476 nextChar();
477 }
479 if (depth != 0)
480 throw new ParseException("dc.unterminated.signature");
482 String sig = newString(pos, bp);
484 // Break sig apart into qualifiedExpr member paramTypes.
485 JCTree qualExpr;
486 Name member;
487 List<JCTree> paramTypes;
489 Log.DeferredDiagnosticHandler deferredDiagnosticHandler
490 = new Log.DeferredDiagnosticHandler(fac.log);
492 try {
493 int hash = sig.indexOf("#");
494 int lparen = sig.indexOf("(", hash + 1);
495 if (hash == -1) {
496 if (lparen == -1) {
497 qualExpr = parseType(sig);
498 member = null;
499 } else {
500 qualExpr = null;
501 member = parseMember(sig.substring(0, lparen));
502 }
503 } else {
504 qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
505 if (lparen == -1)
506 member = parseMember(sig.substring(hash + 1));
507 else
508 member = parseMember(sig.substring(hash + 1, lparen));
509 }
511 if (lparen < 0) {
512 paramTypes = null;
513 } else {
514 int rparen = sig.indexOf(")", lparen);
515 if (rparen != sig.length() - 1)
516 throw new ParseException("dc.ref.bad.parens");
517 paramTypes = parseParams(sig.substring(lparen + 1, rparen));
518 }
520 if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
521 throw new ParseException("dc.ref.syntax.error");
523 } finally {
524 fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
525 }
527 return m.at(pos).Reference(sig, qualExpr, member, paramTypes);
528 }
530 JCTree parseType(String s) throws ParseException {
531 JavacParser p = fac.newParser(s, false, false, false);
532 JCTree tree = p.parseType();
533 if (p.token().kind != TokenKind.EOF)
534 throw new ParseException("dc.ref.unexpected.input");
535 return tree;
536 }
538 Name parseMember(String s) throws ParseException {
539 JavacParser p = fac.newParser(s, false, false, false);
540 Name name = p.ident();
541 if (p.token().kind != TokenKind.EOF)
542 throw new ParseException("dc.ref.unexpected.input");
543 return name;
544 }
546 List<JCTree> parseParams(String s) throws ParseException {
547 if (s.trim().isEmpty())
548 return List.nil();
550 JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
551 ListBuffer<JCTree> paramTypes = new ListBuffer<JCTree>();
552 paramTypes.add(p.parseType());
554 if (p.token().kind == TokenKind.IDENTIFIER)
555 p.nextToken();
557 while (p.token().kind == TokenKind.COMMA) {
558 p.nextToken();
559 paramTypes.add(p.parseType());
561 if (p.token().kind == TokenKind.IDENTIFIER)
562 p.nextToken();
563 }
565 if (p.token().kind != TokenKind.EOF)
566 throw new ParseException("dc.ref.unexpected.input");
568 return paramTypes.toList();
569 }
571 /**
572 * Read Java identifier
573 * Matching pairs of { } are skipped; the text is terminated by the first
574 * unmatched }. It is an error if the beginning of the next tag is detected.
575 */
576 @SuppressWarnings("fallthrough")
577 protected DCIdentifier identifier() throws ParseException {
578 skipWhitespace();
579 int pos = bp;
581 if (isJavaIdentifierStart(ch)) {
582 nextChar();
583 while (isJavaIdentifierPart(ch))
584 nextChar();
585 return m.at(pos).Identifier(names.fromChars(buf, pos, bp - pos));
586 }
588 throw new ParseException("dc.identifier.expected");
589 }
591 /**
592 * Read a quoted string.
593 * It is an error if the beginning of the next tag is detected.
594 */
595 @SuppressWarnings("fallthrough")
596 protected DCText quotedString() {
597 int pos = bp;
598 nextChar();
600 loop:
601 while (bp < buflen) {
602 switch (ch) {
603 case '\n': case '\r': case '\f':
604 newline = true;
605 break;
607 case ' ': case '\t':
608 break;
610 case '"':
611 nextChar();
612 // trim trailing white-space?
613 return m.at(pos).Text(newString(pos, bp));
615 case '@':
616 if (newline)
617 break loop;
619 }
620 nextChar();
621 }
622 return null;
623 }
625 /**
626 * Read general text content of an inline tag, including HTML entities and elements.
627 * Matching pairs of { } are skipped; the text is terminated by the first
628 * unmatched }. It is an error if the beginning of the next tag is detected.
629 */
630 @SuppressWarnings("fallthrough")
631 protected List<DCTree> inlineContent() {
632 ListBuffer<DCTree> trees = new ListBuffer<DCTree>();
634 skipWhitespace();
635 int pos = bp;
636 int depth = 1;
637 textStart = -1;
639 loop:
640 while (bp < buflen) {
642 switch (ch) {
643 case '\n': case '\r': case '\f':
644 newline = true;
645 // fall through
647 case ' ': case '\t':
648 nextChar();
649 break;
651 case '&':
652 entity(trees);
653 break;
655 case '<':
656 newline = false;
657 addPendingText(trees, bp - 1);
658 trees.add(html());
659 break;
661 case '{':
662 newline = false;
663 depth++;
664 nextChar();
665 break;
667 case '}':
668 newline = false;
669 if (--depth == 0) {
670 addPendingText(trees, bp - 1);
671 nextChar();
672 return trees.toList();
673 }
674 nextChar();
675 break;
677 case '@':
678 if (newline)
679 break loop;
680 // fallthrough
682 default:
683 if (textStart == -1)
684 textStart = bp;
685 nextChar();
686 break;
687 }
688 }
690 return List.<DCTree>of(erroneous("dc.unterminated.inline.tag", pos));
691 }
693 protected void entity(ListBuffer<DCTree> list) {
694 newline = false;
695 addPendingText(list, bp - 1);
696 list.add(entity());
697 if (textStart == -1) {
698 textStart = bp;
699 lastNonWhite = -1;
700 }
701 }
703 /**
704 * Read an HTML entity.
705 * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
706 */
707 protected DCTree entity() {
708 int p = bp;
709 nextChar();
710 int namep = bp;
711 boolean checkSemi = false;
712 if (ch == '#') {
713 nextChar();
714 if (isDecimalDigit(ch)) {
715 nextChar();
716 while (isDecimalDigit(ch))
717 nextChar();
718 checkSemi = true;
719 } else if (ch == 'x' || ch == 'X') {
720 nextChar();
721 if (isHexDigit(ch)) {
722 nextChar();
723 while (isHexDigit(ch))
724 nextChar();
725 checkSemi = true;
726 }
727 }
728 } else if (isIdentifierStart(ch)) {
729 nextChar();
730 while (isIdentifierPart(ch))
731 nextChar();
732 checkSemi = true;
733 }
735 if (checkSemi && ch == ';') {
736 nextChar();
737 return m.at(p).Entity(names.fromChars(buf, namep, bp - namep - 1));
738 } else {
739 String code = checkSemi ? "dc.missing.semicolon" : "dc.bad.entity";
740 return erroneous(code, p);
741 }
742 }
744 /**
745 * Read the start or end of an HTML tag, or an HTML comment
746 * {@literal <identifier attrs> } or {@literal </identifier> }
747 */
748 protected DCTree html() {
749 int p = bp;
750 nextChar();
751 if (isIdentifierStart(ch)) {
752 int namePos = bp;
753 nextChar();
754 while (isIdentifierPart(ch))
755 nextChar();
756 int nameLen = bp - namePos;
757 List<DCTree> attrs = htmlAttrs();
758 if (attrs != null) {
759 boolean selfClosing = false;
760 if (ch == '/') {
761 nextChar();
762 selfClosing = true;
763 }
764 if (ch == '>') {
765 nextChar();
766 Name name = names.fromChars(buf, namePos, nameLen);
767 return m.at(p).StartElement(name, attrs, selfClosing);
768 }
769 }
770 } else if (ch == '/') {
771 nextChar();
772 if (isIdentifierStart(ch)) {
773 int namePos = bp;
774 nextChar();
775 while (isIdentifierPart(ch))
776 nextChar();
777 int nameLen = bp - namePos;
778 skipWhitespace();
779 if (ch == '>') {
780 nextChar();
781 Name name = names.fromChars(buf, namePos, nameLen);
782 return m.at(p).EndElement(name);
783 }
784 }
785 } else if (ch == '!') {
786 nextChar();
787 if (ch == '-') {
788 nextChar();
789 if (ch == '-') {
790 nextChar();
791 while (bp < buflen) {
792 int dash = 0;
793 while (ch == '-') {
794 dash++;
795 nextChar();
796 }
797 // strictly speaking, a comment should not contain "--"
798 // so dash > 2 is an error, dash == 2 implies ch == '>'
799 if (dash >= 2 && ch == '>') {
800 nextChar();
801 return m.at(p).Comment(newString(p, bp));
802 }
804 nextChar();
805 }
806 }
807 }
808 }
810 bp = p + 1;
811 ch = buf[bp];
812 return erroneous("dc.malformed.html", p);
813 }
815 /**
816 * Read a series of HTML attributes, terminated by {@literal > }.
817 * Each attribute is of the form {@literal identifier[=value] }.
818 * "value" may be unquoted, single-quoted, or double-quoted.
819 */
820 protected List<DCTree> htmlAttrs() {
821 ListBuffer<DCTree> attrs = new ListBuffer<DCTree>();
822 skipWhitespace();
824 loop:
825 while (isIdentifierStart(ch)) {
826 int namePos = bp;
827 nextChar();
828 while (isIdentifierPart(ch))
829 nextChar();
830 int nameLen = bp - namePos;
831 skipWhitespace();
832 List<DCTree> value = null;
833 ValueKind vkind = ValueKind.EMPTY;
834 if (ch == '=') {
835 ListBuffer<DCTree> v = new ListBuffer<DCTree>();
836 nextChar();
837 skipWhitespace();
838 if (ch == '\'' || ch == '"') {
839 vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE;
840 char quote = ch;
841 nextChar();
842 textStart = bp;
843 while (bp < buflen && ch != quote) {
844 if (newline && ch == '@') {
845 attrs.add(erroneous("dc.unterminated.string", namePos));
846 // No point trying to read more.
847 // In fact, all attrs get discarded by the caller
848 // and superseded by a malformed.html node because
849 // the html tag itself is not terminated correctly.
850 break loop;
851 }
852 attrValueChar(v);
853 }
854 addPendingText(v, bp - 1);
855 nextChar();
856 } else {
857 vkind = ValueKind.UNQUOTED;
858 textStart = bp;
859 while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
860 attrValueChar(v);
861 }
862 addPendingText(v, bp - 1);
863 }
864 skipWhitespace();
865 value = v.toList();
866 }
867 Name name = names.fromChars(buf, namePos, nameLen);
868 DCAttribute attr = m.at(namePos).Attribute(name, vkind, value);
869 attrs.add(attr);
870 }
872 return attrs.toList();
873 }
875 protected void attrValueChar(ListBuffer<DCTree> list) {
876 switch (ch) {
877 case '&':
878 entity(list);
879 break;
881 case '{':
882 inlineTag(list);
883 break;
885 default:
886 nextChar();
887 }
888 }
890 protected void addPendingText(ListBuffer<DCTree> list, int textEnd) {
891 if (textStart != -1 && textStart <= textEnd) {
892 list.add(m.at(textStart).Text(newString(textStart, textEnd + 1)));
893 textStart = -1;
894 }
895 }
897 protected DCErroneous erroneous(String code, int pos) {
898 int i = bp - 1;
899 loop:
900 while (i > 0) {
901 switch (buf[i]) {
902 case '\f': case '\n': case '\r':
903 newline = true;
904 break;
905 case '\t': case ' ':
906 break;
907 default:
908 break loop;
909 }
910 i--;
911 }
912 textStart = -1;
913 return m.at(pos).Erroneous(newString(pos, i + 1), diagSource, code);
914 }
916 @SuppressWarnings("unchecked")
917 <T> T getFirst(List<T>... lists) {
918 for (List<T> list: lists) {
919 if (list.nonEmpty())
920 return list.head;
921 }
922 return null;
923 }
925 protected boolean isIdentifierStart(char ch) {
926 return Character.isUnicodeIdentifierStart(ch);
927 }
929 protected boolean isIdentifierPart(char ch) {
930 return Character.isUnicodeIdentifierPart(ch);
931 }
933 protected boolean isJavaIdentifierStart(char ch) {
934 return Character.isJavaIdentifierStart(ch);
935 }
937 protected boolean isJavaIdentifierPart(char ch) {
938 return Character.isJavaIdentifierPart(ch);
939 }
941 protected boolean isDecimalDigit(char ch) {
942 return ('0' <= ch && ch <= '9');
943 }
945 protected boolean isHexDigit(char ch) {
946 return ('0' <= ch && ch <= '9')
947 || ('a' <= ch && ch <= 'f')
948 || ('A' <= ch && ch <= 'F');
949 }
951 protected boolean isUnquotedAttrValueTerminator(char ch) {
952 switch (ch) {
953 case '\f': case '\n': case '\r': case '\t':
954 case ' ':
955 case '"': case '\'': case '`':
956 case '=': case '<': case '>':
957 return true;
958 default:
959 return false;
960 }
961 }
963 protected boolean isWhitespace(char ch) {
964 return Character.isWhitespace(ch);
965 }
967 protected void skipWhitespace() {
968 while (isWhitespace(ch))
969 nextChar();
970 }
972 protected int getSentenceBreak(String s) {
973 if (sentenceBreaker != null) {
974 sentenceBreaker.setText(s);
975 int i = sentenceBreaker.next();
976 return (i == s.length()) ? -1 : i;
977 }
979 // scan for period followed by whitespace
980 boolean period = false;
981 for (int i = 0; i < s.length(); i++) {
982 switch (s.charAt(i)) {
983 case '.':
984 period = true;
985 break;
987 case ' ':
988 case '\f':
989 case '\n':
990 case '\r':
991 case '\t':
992 if (period)
993 return i;
994 break;
996 default:
997 period = false;
998 break;
999 }
1000 }
1001 return -1;
1002 }
1005 Set<String> htmlBlockTags = new HashSet<String>(Arrays.asList(
1006 "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"));
1008 protected boolean isSentenceBreak(Name n) {
1009 return htmlBlockTags.contains(n.toString().toLowerCase());
1010 }
1012 protected boolean isSentenceBreak(DCTree t) {
1013 switch (t.getKind()) {
1014 case START_ELEMENT:
1015 return isSentenceBreak(((DCStartElement) t).getName());
1017 case END_ELEMENT:
1018 return isSentenceBreak(((DCEndElement) t).getName());
1019 }
1020 return false;
1021 }
1023 /**
1024 * @param start position of first character of string
1025 * @param end position of character beyond last character to be included
1026 */
1027 String newString(int start, int end) {
1028 return new String(buf, start, end - start);
1029 }
1031 static abstract class TagParser {
1032 enum Kind { INLINE, BLOCK }
1034 Kind kind;
1035 DCTree.Kind treeKind;
1037 TagParser(Kind k, DCTree.Kind tk) {
1038 kind = k;
1039 treeKind = tk;
1040 }
1042 Kind getKind() {
1043 return kind;
1044 }
1046 DCTree.Kind getTreeKind() {
1047 return treeKind;
1048 }
1050 abstract DCTree parse(int pos) throws ParseException;
1051 }
1053 /**
1054 * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
1055 */
1056 private void initTagParsers() {
1057 TagParser[] parsers = {
1058 // @author name-text
1059 new TagParser(Kind.BLOCK, DCTree.Kind.AUTHOR) {
1060 public DCTree parse(int pos) {
1061 List<DCTree> name = blockContent();
1062 return m.at(pos).Author(name);
1063 }
1064 },
1066 // {@code text}
1067 new TagParser(Kind.INLINE, DCTree.Kind.CODE) {
1068 public DCTree parse(int pos) throws ParseException {
1069 DCTree text = inlineText();
1070 nextChar();
1071 return m.at(pos).Code((DCText) text);
1072 }
1073 },
1075 // @deprecated deprecated-text
1076 new TagParser(Kind.BLOCK, DCTree.Kind.DEPRECATED) {
1077 public DCTree parse(int pos) {
1078 List<DCTree> reason = blockContent();
1079 return m.at(pos).Deprecated(reason);
1080 }
1081 },
1083 // {@docRoot}
1084 new TagParser(Kind.INLINE, DCTree.Kind.DOC_ROOT) {
1085 public DCTree parse(int pos) throws ParseException {
1086 if (ch == '}') {
1087 nextChar();
1088 return m.at(pos).DocRoot();
1089 }
1090 inlineText(); // skip unexpected content
1091 nextChar();
1092 throw new ParseException("dc.unexpected.content");
1093 }
1094 },
1096 // @exception class-name description
1097 new TagParser(Kind.BLOCK, DCTree.Kind.EXCEPTION) {
1098 public DCTree parse(int pos) throws ParseException {
1099 skipWhitespace();
1100 DCReference ref = reference(false);
1101 List<DCTree> description = blockContent();
1102 return m.at(pos).Exception(ref, description);
1103 }
1104 },
1106 // {@inheritDoc}
1107 new TagParser(Kind.INLINE, DCTree.Kind.INHERIT_DOC) {
1108 public DCTree parse(int pos) throws ParseException {
1109 if (ch == '}') {
1110 nextChar();
1111 return m.at(pos).InheritDoc();
1112 }
1113 inlineText(); // skip unexpected content
1114 nextChar();
1115 throw new ParseException("dc.unexpected.content");
1116 }
1117 },
1119 // {@link package.class#member label}
1120 new TagParser(Kind.INLINE, DCTree.Kind.LINK) {
1121 public DCTree parse(int pos) throws ParseException {
1122 DCReference ref = reference(true);
1123 List<DCTree> label = inlineContent();
1124 return m.at(pos).Link(ref, label);
1125 }
1126 },
1128 // {@linkplain package.class#member label}
1129 new TagParser(Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
1130 public DCTree parse(int pos) throws ParseException {
1131 DCReference ref = reference(true);
1132 List<DCTree> label = inlineContent();
1133 return m.at(pos).LinkPlain(ref, label);
1134 }
1135 },
1137 // {@literal text}
1138 new TagParser(Kind.INLINE, DCTree.Kind.LITERAL) {
1139 public DCTree parse(int pos) throws ParseException {
1140 DCTree text = inlineText();
1141 nextChar();
1142 return m.at(pos).Literal((DCText) text);
1143 }
1144 },
1146 // @param parameter-name description
1147 new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) {
1148 public DCTree parse(int pos) throws ParseException {
1149 skipWhitespace();
1151 boolean typaram = false;
1152 if (ch == '<') {
1153 typaram = true;
1154 nextChar();
1155 }
1157 DCIdentifier id = identifier();
1159 if (typaram) {
1160 if (ch != '>')
1161 throw new ParseException("dc.gt.expected");
1162 nextChar();
1163 }
1165 skipWhitespace();
1166 List<DCTree> desc = blockContent();
1167 return m.at(pos).Param(typaram, id, desc);
1168 }
1169 },
1171 // @return description
1172 new TagParser(Kind.BLOCK, DCTree.Kind.RETURN) {
1173 public DCTree parse(int pos) {
1174 List<DCTree> description = blockContent();
1175 return m.at(pos).Return(description);
1176 }
1177 },
1179 // @see reference | quoted-string | HTML
1180 new TagParser(Kind.BLOCK, DCTree.Kind.SEE) {
1181 public DCTree parse(int pos) throws ParseException {
1182 skipWhitespace();
1183 switch (ch) {
1184 case '"':
1185 DCText string = quotedString();
1186 if (string != null) {
1187 skipWhitespace();
1188 if (ch == '@')
1189 return m.at(pos).See(List.<DCTree>of(string));
1190 }
1191 break;
1193 case '<':
1194 List<DCTree> html = blockContent();
1195 if (html != null)
1196 return m.at(pos).See(html);
1197 break;
1199 default:
1200 if (isJavaIdentifierStart(ch) || ch == '#') {
1201 DCReference ref = reference(true);
1202 List<DCTree> description = blockContent();
1203 return m.at(pos).See(description.prepend(ref));
1204 }
1205 }
1206 throw new ParseException("dc.unexpected.content");
1207 }
1208 },
1210 // @serialData data-description
1211 new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_DATA) {
1212 public DCTree parse(int pos) {
1213 List<DCTree> description = blockContent();
1214 return m.at(pos).SerialData(description);
1215 }
1216 },
1218 // @serialField field-name field-type description
1219 new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_FIELD) {
1220 public DCTree parse(int pos) throws ParseException {
1221 skipWhitespace();
1222 DCIdentifier name = identifier();
1223 skipWhitespace();
1224 DCReference type = reference(false);
1225 List<DCTree> description = null;
1226 if (isWhitespace(ch)) {
1227 skipWhitespace();
1228 description = blockContent();
1229 }
1230 return m.at(pos).SerialField(name, type, description);
1231 }
1232 },
1234 // @serial field-description | include | exclude
1235 new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL) {
1236 public DCTree parse(int pos) {
1237 List<DCTree> description = blockContent();
1238 return m.at(pos).Serial(description);
1239 }
1240 },
1242 // @since since-text
1243 new TagParser(Kind.BLOCK, DCTree.Kind.SINCE) {
1244 public DCTree parse(int pos) {
1245 List<DCTree> description = blockContent();
1246 return m.at(pos).Since(description);
1247 }
1248 },
1250 // @throws class-name description
1251 new TagParser(Kind.BLOCK, DCTree.Kind.THROWS) {
1252 public DCTree parse(int pos) throws ParseException {
1253 skipWhitespace();
1254 DCReference ref = reference(false);
1255 List<DCTree> description = blockContent();
1256 return m.at(pos).Throws(ref, description);
1257 }
1258 },
1260 // {@value package.class#field}
1261 new TagParser(Kind.INLINE, DCTree.Kind.VALUE) {
1262 public DCTree parse(int pos) throws ParseException {
1263 DCReference ref = reference(true);
1264 skipWhitespace();
1265 if (ch == '}') {
1266 nextChar();
1267 return m.at(pos).Value(ref);
1268 }
1269 nextChar();
1270 throw new ParseException("dc.unexpected.content");
1271 }
1272 },
1274 // @version version-text
1275 new TagParser(Kind.BLOCK, DCTree.Kind.VERSION) {
1276 public DCTree parse(int pos) {
1277 List<DCTree> description = blockContent();
1278 return m.at(pos).Version(description);
1279 }
1280 },
1281 };
1283 tagParsers = new HashMap<Name,TagParser>();
1284 for (TagParser p: parsers)
1285 tagParsers.put(names.fromString(p.getTreeKind().tagName), p);
1287 }
1288 }