1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.parser;
13 import java.util.ArrayList;
14 import java.util.List;
16 import org.eclipse.jdt.core.compiler.CharOperation;
17 import org.eclipse.jdt.core.compiler.InvalidInputException;
18 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
21 * Parser specialized for decoding javadoc comments
23 public abstract class AbstractCommentParser {
26 public static final char[] TAG_DEPRECATED = "deprecated".toCharArray(); //$NON-NLS-1$
27 public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
28 public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
29 public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
30 public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
31 public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
32 public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
33 public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
34 public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
35 public static final char[] TAG_VALUE = "value".toCharArray(); //$NON-NLS-1$
38 public static final int NO_TAG_VALUE = 0;
39 public static final int TAG_DEPRECATED_VALUE = 1;
40 public static final int TAG_PARAM_VALUE = 2;
41 public static final int TAG_RETURN_VALUE = 3;
42 public static final int TAG_THROWS_VALUE = 4;
43 public static final int TAG_EXCEPTION_VALUE = 5;
44 public static final int TAG_SEE_VALUE = 6;
45 public static final int TAG_LINK_VALUE = 7;
46 public static final int TAG_LINKPLAIN_VALUE = 8;
47 public static final int TAG_INHERITDOC_VALUE = 9;
48 public static final int TAG_VALUE_VALUE = 10;
49 public static final int TAG_OTHERS_VALUE = 11;
50 protected int tagValue = NO_TAG_VALUE;
52 // tags expected positions
53 public final static int ORDERED_TAGS_NUMBER = 3;
54 public final static int PARAM_TAG_EXPECTED_ORDER = 0;
55 public final static int THROWS_TAG_EXPECTED_ORDER = 1;
56 public final static int SEE_TAG_EXPECTED_ORDER = 2;
58 // Kind of comment parser
59 public final static int COMPIL_PARSER = 0x00000001;
60 public final static int DOM_PARSER = 0x00000002;
63 public Scanner scanner;
65 protected Parser sourceParser;
66 private int currentTokenType = -1;
69 public boolean checkDocComment = false;
70 public boolean reportProblems;
71 protected boolean jdk15;
74 protected boolean inherited, deprecated;
75 protected Object returnStatement;
78 protected int index, endComment, lineEnd;
79 protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
80 protected int textStart, memberStart;
81 protected int tagSourceStart, tagSourceEnd;
82 protected int inlineTagStart;
83 protected int[] lineEnds;
86 protected boolean lineStarted = false, inlineTagStarted = false;
90 private int linePtr, lastLinePtr;
93 protected int identifierPtr;
94 protected char[][] identifierStack;
95 protected int identifierLengthPtr;
96 protected int[] identifierLengthStack;
97 protected long[] identifierPositionStack;
99 protected static int AstStackIncrement = 10;
100 protected int astPtr;
101 protected Object[] astStack;
102 protected int astLengthPtr;
103 protected int[] astLengthStack;
105 protected AbstractCommentParser(Parser sourceParser) {
106 this.sourceParser = sourceParser;
107 this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/);
108 this.identifierStack = new char[20][];
109 this.identifierPositionStack = new long[20];
110 this.identifierLengthStack = new int[10];
111 this.astStack = new Object[30];
112 this.astLengthStack = new int[20];
113 this.reportProblems = sourceParser != null;
117 * Returns true if tag @deprecated is present in javadoc comment.
119 * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
120 * slot for being consumed later on.
122 protected boolean commentParse(int javadocStart, int javadocEnd) {
124 boolean validComment = true;
126 // Init scanner position
127 this.scanner.resetTo(javadocStart, javadocEnd);
128 this.endComment = javadocEnd;
129 this.index = javadocStart;
130 readChar(); // starting '/'
131 int previousPosition = this.index;
132 readChar(); // first '*'
133 char nextCharacter= readChar(); // second '*'
135 // Init local variables
136 this.astLengthPtr = -1;
138 this.currentTokenType = -1;
139 this.inlineTagStarted = false;
140 this.inlineTagStart = -1;
141 this.lineStarted = false;
142 this.returnStatement = null;
143 this.inherited = false;
144 this.deprecated = false;
145 this.linePtr = getLineNumber(javadocStart);
146 this.lastLinePtr = getLineNumber(javadocEnd);
147 this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
149 char previousChar = 0;
150 int invalidTagLineEnd = -1;
151 int invalidInlineTagLineEnd = -1;
153 // Loop on each comment character
154 while (this.index < this.endComment) {
155 previousPosition = this.index;
156 previousChar = nextCharacter;
158 // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
159 if (this.index > (this.lineEnd+1)) {
163 // Read next char only if token was consumed
164 if (this.currentTokenType < 0) {
165 nextCharacter = readChar(); // consider unicodes
167 previousPosition = this.scanner.getCurrentTokenStartPosition();
168 switch (this.currentTokenType) {
169 case TerminalTokens.TokenNameRBRACE:
172 case TerminalTokens.TokenNameMULTIPLY:
176 nextCharacter = this.scanner.currentCharacter;
181 if (this.index >= this.endComment) {
185 switch (nextCharacter) {
187 // Start tag parsing only if we are on line beginning or at inline tag beginning
188 if ((!this.lineStarted || previousChar == '{')) {
189 this.lineStarted = true;
190 if (this.inlineTagStarted) {
191 this.inlineTagStarted = false;
192 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
193 // Cannot have @ inside inline comment
194 if (this.reportProblems) {
195 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
196 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
198 validComment = false;
199 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
200 pushText(this.textStart, previousPosition);
202 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
204 if (previousChar == '{') {
205 if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
206 pushText(this.textStart, this.inlineTagStart);
208 this.inlineTagStarted = true;
209 invalidInlineTagLineEnd = this.lineEnd;
210 } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
211 pushText(this.textStart, invalidTagLineEnd);
213 this.scanner.resetTo(this.index, this.endComment);
214 this.currentTokenType = -1; // flush token cache at line begin
216 if (!parseTag(previousPosition)) {
217 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
218 // do not stop the inline tag when error is encountered to get text after
219 validComment = false;
220 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
221 // for DOM AST node, store tag as text in case of invalid syntax
222 if (this.kind == DOM_PARSER) {
224 this.textStart = this.tagSourceEnd+1;
225 invalidTagLineEnd = this.lineEnd;
228 } catch (InvalidInputException e) {
235 if (this.lineStarted && this.textStart < previousPosition) {
236 pushText(this.textStart, previousPosition);
238 this.lineStarted = false;
243 if (this.inlineTagStarted) {
244 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
245 pushText(this.textStart, previousPosition);
247 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
248 this.textStart = this.index;
249 this.inlineTagStarted = false;
251 if (!this.lineStarted) {
252 this.textStart = previousPosition;
255 this.lineStarted = true;
258 if (this.inlineTagStarted) {
259 this.inlineTagStarted = false;
260 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
261 // Cannot have opening brace in inline comment
262 if (this.reportProblems) {
263 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
264 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
266 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
267 pushText(this.textStart, previousPosition);
269 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
271 if (!this.lineStarted) {
272 this.textStart = previousPosition;
274 this.lineStarted = true;
275 this.inlineTagStart = previousPosition;
278 case '\u000c' : /* FORM FEED */
279 case ' ' : /* SPACE */
280 case '\t' : /* HORIZONTAL TABULATION */
281 // do nothing for space or '*' characters
284 if (!this.lineStarted) {
285 this.textStart = previousPosition;
287 this.lineStarted = true;
291 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
292 // Cannot leave comment inside inline comment
293 if (this.inlineTagStarted) {
294 this.inlineTagStarted = false;
295 if (this.reportProblems) {
296 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
297 if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
298 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
300 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
301 pushText(this.textStart, previousPosition);
303 if (this.kind == DOM_PARSER) {
304 refreshInlineTagPosition(previousPosition);
306 } else if (this.lineStarted && this.textStart < previousPosition) {
307 pushText(this.textStart, previousPosition);
310 } catch (Exception ex) {
311 validComment = false;
316 private void consumeToken() {
317 this.currentTokenType = -1; // flush token cache
321 protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
322 protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
323 protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
324 protected Object createReturnStatement() { return null; }
325 protected abstract void createTag();
326 protected abstract Object createTypeReference(int primitiveToken);
328 private int getIndexPosition() {
329 if (this.index > this.lineEnd) {
337 * Search the line number corresponding to a specific position.
338 * Warning: returned position is 1-based index!
339 * @see Scanner#getLineNumber(int) We cannot directly use this method
340 * when linePtr field is not initialized.
342 private int getLineNumber(int position) {
344 if (this.scanner.linePtr != -1) {
345 return this.scanner.getLineNumber(position);
347 if (this.lineEnds == null)
349 int length = this.lineEnds.length;
352 int g = 0, d = length - 1;
356 if (position < this.lineEnds[m]) {
358 } else if (position > this.lineEnds[m]) {
364 if (position < this.lineEnds[m]) {
370 private int getTokenEndPosition() {
371 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
374 return this.scanner.getCurrentTokenEndPosition();
379 * Parse argument in @see tag method reference
381 private Object parseArguments(Object receiver) throws InvalidInputException {
384 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
386 char[] argName = null;
387 List arguments = new ArrayList(10);
388 int start = this.scanner.getCurrentTokenStartPosition();
390 // Parse arguments declaration if method reference
391 nextArg : while (this.index < this.scanner.eofPosition) {
393 // Read argument type reference
396 typeRef = parseQualifiedName(false);
397 } catch (InvalidInputException e) {
400 boolean firstArg = modulo == 0;
401 if (firstArg) { // verify position
404 } else if ((iToken % modulo) != 0) {
407 if (typeRef == null) {
408 if (firstArg && this.currentTokenType == TerminalTokens.TokenNameRPAREN) {
409 // verify characters after arguments declaration (expecting white space or end comment)
410 if (!verifySpaceOrEndComment()) {
411 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
412 if (this.source[end]=='\n') end--;
413 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
416 this.lineStarted = true;
417 return createMethodReference(receiver, null);
423 // Read possible array declaration
425 long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
426 if (readToken() == TerminalTokens.TokenNameLBRACKET) {
427 int dimStart = this.scanner.getCurrentTokenStartPosition();
428 while (readToken() == TerminalTokens.TokenNameLBRACKET) {
430 if (readToken() != TerminalTokens.TokenNameRBRACKET) {
434 dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
438 // Read argument name
439 long argNamePos = -1;
440 if (readToken() == TerminalTokens.TokenNameIdentifier) {
442 if (firstArg) { // verify position
445 } else if ((iToken % modulo) != 1) {
448 if (argName == null) { // verify that all arguments name are declared
453 argName = this.scanner.getCurrentIdentifierSource();
454 argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
456 } else if (argName != null) { // verify that no argument name is declared
460 // Verify token position
464 if ((iToken % modulo) != (modulo - 1)) {
469 // Read separator or end arguments declaration
470 int token = readToken();
471 char[] name = argName == null ? new char[0] : argName;
472 if (token == TerminalTokens.TokenNameCOMMA) {
473 // Create new argument
474 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
475 arguments.add(argument);
478 } else if (token == TerminalTokens.TokenNameRPAREN) {
479 // verify characters after arguments declaration (expecting white space or end comment)
480 if (!verifySpaceOrEndComment()) {
481 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
482 if (this.source[end]=='\n') end--;
483 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
486 // Create new argument
487 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
488 arguments.add(argument);
490 return createMethodReference(receiver, arguments);
496 // Something wrong happened => Invalid input
497 throw new InvalidInputException();
501 * Parse an URL link reference in @see tag
503 private boolean parseHref() throws InvalidInputException {
504 int start = this.scanner.getCurrentTokenStartPosition();
505 if (Character.toLowerCase(readChar()) == 'a') {
506 this.scanner.currentPosition = this.index;
507 if (readToken() == TerminalTokens.TokenNameIdentifier) {
510 if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
511 readToken() == TerminalTokens.TokenNameEQUAL) {
513 if (readToken() == TerminalTokens.TokenNameStringLiteral) {
515 // Skip all characters after string literal until closing '>' (see bug 68726)
516 while (readToken() != TerminalTokens.TokenNameGREATER) {
517 if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' ||
518 (this.inlineTagStarted && this.scanner.currentCharacter == '}')) {
519 // Reset position: we want to rescan last token
520 this.index = this.tokenPreviousPosition;
521 this.scanner.currentPosition = this.tokenPreviousPosition;
522 this.currentTokenType = -1;
523 // Signal syntax error
524 if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
525 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
529 this.currentTokenType = -1; // do not update line end
531 if (this.currentTokenType == TerminalTokens.TokenNameGREATER) {
532 consumeToken(); // update line end as new lines are allowed in URL description
533 while (readToken() != TerminalTokens.TokenNameLESS) {
534 if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' ||
535 (this.inlineTagStarted && this.scanner.currentCharacter == '}')) {
536 // Reset position: we want to rescan last token
537 this.index = this.tokenPreviousPosition;
538 this.scanner.currentPosition = this.tokenPreviousPosition;
539 this.currentTokenType = -1;
540 // Signal syntax error
541 if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
542 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
549 start = this.scanner.getCurrentTokenStartPosition();
550 if (readChar() == '/') {
551 if (Character.toLowerCase(readChar()) == 'a') {
552 if (readChar() == '>') {
561 } catch (InvalidInputException ex) {
562 // Do nothing as we want to keep positions for error message
566 // Reset position: we want to rescan last token
567 this.index = this.tokenPreviousPosition;
568 this.scanner.currentPosition = this.tokenPreviousPosition;
569 this.currentTokenType = -1;
570 // Signal syntax error
571 if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
572 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
578 * Parse a method reference in @see tag
580 private Object parseMember(Object receiver) throws InvalidInputException {
582 this.identifierPtr = -1;
583 this.identifierLengthPtr = -1;
584 int start = this.scanner.getCurrentTokenStartPosition();
585 this.memberStart = start;
587 // Get member identifier
588 if (readToken() == TerminalTokens.TokenNameIdentifier) {
590 pushIdentifier(true);
591 // Look for next token to know whether it's a field or method reference
592 int previousPosition = this.index;
593 if (readToken() == TerminalTokens.TokenNameLPAREN) {
595 start = this.scanner.getCurrentTokenStartPosition();
597 return parseArguments(receiver);
598 } catch (InvalidInputException e) {
599 int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
600 this.scanner.getCurrentTokenEndPosition() :
601 this.scanner.getCurrentTokenStartPosition();
602 end = end < this.lineEnd ? end : this.lineEnd;
603 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
608 // Reset position: we want to rescan last token
609 this.index = previousPosition;
610 this.scanner.currentPosition = previousPosition;
611 this.currentTokenType = -1;
613 // Verify character(s) after identifier (expecting space or end comment)
614 if (!verifySpaceOrEndComment()) {
615 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
616 if (this.source[end]=='\n') end--;
617 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
620 return createFieldReference(receiver);
622 int end = getTokenEndPosition() - 1;
623 end = start > end ? start : end;
624 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(start, end);
625 // Reset position: we want to rescan last token
626 this.index = this.tokenPreviousPosition;
627 this.scanner.currentPosition = this.tokenPreviousPosition;
628 this.currentTokenType = -1;
633 * Parse @param tag declaration
635 protected boolean parseParam() throws InvalidInputException {
637 // Store current state
638 int start = this.tagSourceStart;
639 int end = this.tagSourceEnd;
640 boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
641 this.scanner.tokenizeWhiteSpace = true;
642 // this.scanner.tokenizeLineSeparator = true;
644 // Verify that there are whitespaces after tag
645 int token = readToken();
646 if (token != TerminalTokens.TokenNameWHITESPACE) {
647 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(start, this.scanner.getCurrentTokenEndPosition());
648 this.scanner.currentPosition = start;
650 this.currentTokenType = -1;
651 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
655 // Get first non whitespace token
656 this.identifierPtr = -1;
657 this.identifierLengthPtr = -1;
658 boolean hasMultiLines = this.scanner.currentPosition > (this.lineEnd+1);
659 boolean isTypeParam = false;
660 boolean valid = true, empty = true;
661 nextToken: while (true) {
662 this.currentTokenType = -1;
665 } catch (InvalidInputException e) {
669 case TerminalTokens.TokenNameIdentifier :
671 // store param name id
672 pushIdentifier(true);
673 start = this.scanner.getCurrentTokenStartPosition();
674 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
677 // fall through next case to report error
678 case TerminalTokens.TokenNameLESS:
679 if (valid && this.jdk15) {
680 // store '<' in identifiers stack as we need to add it to tag element (bug 79809)
681 pushIdentifier(true);
682 start = this.scanner.getCurrentTokenStartPosition();
683 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
687 // fall through next case to report error
689 if (token == TerminalTokens.TokenNameLEFT_SHIFT) isTypeParam = true;
690 if (valid && !hasMultiLines) start = this.scanner.getCurrentTokenStartPosition();
692 if (!hasMultiLines) {
694 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
698 // when several lines, fall through next case to report problem immediately
699 case TerminalTokens.TokenNameWHITESPACE:
700 if (this.scanner.currentPosition > (this.lineEnd+1)) hasMultiLines = true;
702 // if not valid fall through next case to report error
703 case TerminalTokens.TokenNameEOF:
704 if (this.reportProblems)
706 this.sourceParser.problemReporter().javadocMissingParamName(start, end, this.sourceParser.modifiers);
707 else if (this.jdk15 && isTypeParam)
708 this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
710 this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
711 this.scanner.currentPosition = start;
713 this.currentTokenType = -1;
714 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
719 // Scan more tokens for type parameter declaration
720 if (isTypeParam && this.jdk15) {
721 // Get type parameter name
722 nextToken: while (true) {
723 this.currentTokenType = -1;
726 } catch (InvalidInputException e) {
730 case TerminalTokens.TokenNameWHITESPACE:
731 if (valid && this.scanner.currentPosition <= (this.lineEnd+1)) break;
732 // if not valid fall through next case to report error
733 case TerminalTokens.TokenNameEOF:
734 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
735 this.scanner.currentPosition = start;
737 this.currentTokenType = -1;
738 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
740 case TerminalTokens.TokenNameIdentifier :
741 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
743 // store param name id
744 pushIdentifier(false);
749 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
755 // Get last character of type parameter declaration
756 boolean spaces = false;
757 nextToken: while (true) {
758 this.currentTokenType = -1;
761 } catch (InvalidInputException e) {
765 case TerminalTokens.TokenNameWHITESPACE:
766 if (this.scanner.currentPosition > (this.lineEnd+1)) {
767 // do not accept type parameter declaration on several lines
768 hasMultiLines = true;
773 // if not valid fall through next case to report error
774 case TerminalTokens.TokenNameEOF:
775 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
776 this.scanner.currentPosition = start;
778 this.currentTokenType = -1;
779 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
781 case TerminalTokens.TokenNameGREATER:
782 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
784 // store '>' in identifiers stack as we need to add it to tag element (bug 79809)
785 pushIdentifier(false);
790 if (!spaces) end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
797 // Verify that tag name is well followed by white spaces
799 this.currentTokenType = -1;
800 int restart = this.scanner.currentPosition;
803 } catch (InvalidInputException e) {
806 if (token == TerminalTokens.TokenNameWHITESPACE) {
807 this.scanner.currentPosition = restart;
808 this.index = restart;
809 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
810 return pushParamName(isTypeParam);
815 this.currentTokenType = -1;
816 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
817 while (readToken() != TerminalTokens.TokenNameWHITESPACE) {
818 this.currentTokenType = -1;
819 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
821 if (this.reportProblems)
822 if (this.jdk15 && isTypeParam)
823 this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
825 this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
826 this.scanner.currentPosition = start;
828 this.currentTokenType = -1;
829 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
834 * Parse a qualified name and built a type reference if the syntax is valid.
836 protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
838 // Reset identifier stack if requested
840 this.identifierPtr = -1;
841 this.identifierLengthPtr = -1;
845 int primitiveToken = -1;
846 nextToken : for (int iToken = 0; ; iToken++) {
847 int token = readToken();
849 case TerminalTokens.TokenNameIdentifier :
850 if (((iToken % 2) > 0)) { // identifiers must be odd tokens
853 pushIdentifier(iToken == 0);
857 case TerminalTokens.TokenNameDOT :
858 if ((iToken % 2) == 0) { // dots must be even tokens
859 throw new InvalidInputException();
864 case TerminalTokens.TokenNamevoid :
865 case TerminalTokens.TokenNameboolean :
866 case TerminalTokens.TokenNamebyte :
867 case TerminalTokens.TokenNamechar :
868 case TerminalTokens.TokenNamedouble :
869 case TerminalTokens.TokenNamefloat :
870 case TerminalTokens.TokenNameint :
871 case TerminalTokens.TokenNamelong :
872 case TerminalTokens.TokenNameshort :
874 throw new InvalidInputException();
876 pushIdentifier(true);
877 primitiveToken = token;
885 if ((iToken % 2) == 0) { // cannot leave on a dot
886 // Reset position: we want to rescan last token
887 if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
888 this.index = this.tokenPreviousPosition;
889 this.scanner.currentPosition = this.tokenPreviousPosition;
890 this.currentTokenType = -1;
892 throw new InvalidInputException();
897 // Reset position: we want to rescan last token
898 if (this.currentTokenType != -1) {
899 this.index = this.tokenPreviousPosition;
900 this.scanner.currentPosition = this.tokenPreviousPosition;
901 this.currentTokenType = -1;
903 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
904 return createTypeReference(primitiveToken);
908 * Parse a reference in @see tag
910 protected boolean parseReference() throws InvalidInputException {
911 int currentPosition = this.scanner.currentPosition;
913 Object typeRef = null;
914 Object reference = null;
915 int previousPosition = -1;
916 int typeRefStartPosition = -1;
918 // Get reference tokens
919 nextToken : while (this.index < this.scanner.eofPosition) {
920 previousPosition = this.index;
921 int token = readToken();
923 case TerminalTokens.TokenNameStringLiteral : // @see "string"
925 int start = this.scanner.getCurrentTokenStartPosition();
926 if (this.tagValue == TAG_VALUE_VALUE) {
927 // String reference are not allowed for @value tag
928 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getTokenEndPosition(), this.sourceParser.modifiers);
931 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
932 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
933 if (typeRef != null) {
934 start = this.tagSourceEnd+1;
935 previousPosition = start;
939 if (verifyEndLine(previousPosition)) {
942 if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
944 case TerminalTokens.TokenNameLESS : // @see "<a href="URL#Value">label</a>
946 start = this.scanner.getCurrentTokenStartPosition();
949 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
950 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
951 if (typeRef != null) {
952 start = this.tagSourceEnd+1;
953 previousPosition = start;
956 if (this.tagValue == TAG_VALUE_VALUE) {
957 // String reference are not allowed for @value tag
958 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
962 if (verifyEndLine(previousPosition)) return true;
963 if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
965 else if (this.tagValue == TAG_VALUE_VALUE) {
966 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
969 case TerminalTokens.TokenNameERROR :
970 if (this.scanner.currentCharacter == '#') { // @see ...#member
972 reference = parseMember(typeRef);
973 if (reference != null) {
974 return pushSeeRef(reference);
979 case TerminalTokens.TokenNameIdentifier :
980 if (typeRef == null) {
981 typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
982 typeRef = parseQualifiedName(true);
990 // Verify that we got a reference
991 if (reference == null) reference = typeRef;
992 if (reference == null) {
993 this.index = this.tokenPreviousPosition;
994 this.scanner.currentPosition = this.tokenPreviousPosition;
995 this.currentTokenType = -1;
996 if (this.tagValue == TAG_VALUE_VALUE) {
997 if (this.kind == DOM_PARSER) createTag();
1000 if (this.reportProblems) {
1001 this.sourceParser.problemReporter().javadocMissingReference(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
1006 // Reset position at the end of type reference
1007 this.index = this.lastIdentifierEndPosition+1;
1008 this.scanner.currentPosition = this.index;
1009 this.currentTokenType = -1;
1011 // In case of @value, we have an invalid reference (only static field refs are valid for this tag)
1012 if (this.tagValue == TAG_VALUE_VALUE) {
1013 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(typeRefStartPosition, this.lineEnd);
1017 // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
1018 // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
1019 char ch = peekChar();
1021 if (this.reportProblems) this.sourceParser.problemReporter().javadocMissingHashCharacter(typeRefStartPosition, this.lineEnd, String.valueOf(this.source, typeRefStartPosition, this.lineEnd-typeRefStartPosition+1));
1025 // Verify that we get white space after reference
1026 if (!verifySpaceOrEndComment()) {
1027 this.index = this.tokenPreviousPosition;
1028 this.scanner.currentPosition = this.tokenPreviousPosition;
1029 this.currentTokenType = -1;
1030 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
1031 if (this.source[end]=='\n') end--;
1032 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
1036 // Everything is OK, store reference
1037 return pushSeeRef(reference);
1039 catch (InvalidInputException ex) {
1040 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(currentPosition, getTokenEndPosition());
1042 // Reset position to avoid missing tokens when new line was encountered
1043 this.index = this.tokenPreviousPosition;
1044 this.scanner.currentPosition = this.tokenPreviousPosition;
1045 this.currentTokenType = -1;
1050 * Parse tag declaration
1052 protected abstract boolean parseTag(int previousPosition) throws InvalidInputException;
1055 * Parse @throws tag declaration
1057 protected boolean parseThrows() {
1058 int start = this.scanner.currentPosition;
1060 Object typeRef = parseQualifiedName(true);
1061 if (typeRef == null) {
1062 if (this.reportProblems)
1063 this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
1065 return pushThrowName(typeRef);
1067 } catch (InvalidInputException ex) {
1068 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getTokenEndPosition());
1074 * Return current character without move index position.
1076 protected char peekChar() {
1077 int idx = this.index;
1078 char c = this.source[idx++];
1079 if (c == '\\' && this.source[idx] == 'u') {
1082 while (this.source[idx] == 'u')
1084 if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
1085 || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
1086 || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
1087 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1094 * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
1096 protected void pushIdentifier(boolean newLength) {
1098 int stackLength = this.identifierStack.length;
1099 if (++this.identifierPtr >= stackLength) {
1101 this.identifierStack, 0,
1102 this.identifierStack = new char[stackLength + 10][], 0,
1105 this.identifierPositionStack, 0,
1106 this.identifierPositionStack = new long[stackLength + 10], 0,
1109 this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
1110 this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
1113 stackLength = this.identifierLengthStack.length;
1114 if (++this.identifierLengthPtr >= stackLength) {
1116 this.identifierLengthStack, 0,
1117 this.identifierLengthStack = new int[stackLength + 10], 0,
1120 this.identifierLengthStack[this.identifierLengthPtr] = 1;
1122 this.identifierLengthStack[this.identifierLengthPtr]++;
1127 * Add a new obj on top of the ast stack.
1128 * If new length is required, then add also a new length in length stack.
1130 protected void pushOnAstStack(Object node, boolean newLength) {
1133 this.astLengthStack[++this.astLengthPtr] = 0;
1137 int stackLength = this.astStack.length;
1138 if (++this.astPtr >= stackLength) {
1141 this.astStack = new Object[stackLength + AstStackIncrement], 0,
1143 this.astPtr = stackLength;
1145 this.astStack[this.astPtr] = node;
1148 stackLength = this.astLengthStack.length;
1149 if (++this.astLengthPtr >= stackLength) {
1151 this.astLengthStack, 0,
1152 this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
1155 this.astLengthStack[this.astLengthPtr] = 1;
1157 this.astLengthStack[this.astLengthPtr]++;
1162 * Push a param name in ast node stack.
1164 protected abstract boolean pushParamName(boolean isTypeParam);
1167 * Push a reference statement in ast node stack.
1169 protected abstract boolean pushSeeRef(Object statement);
1172 * Push a text element in ast node stack
1174 protected abstract void pushText(int start, int end);
1177 * Push a throws type ref in ast node stack.
1179 protected abstract boolean pushThrowName(Object typeRef);
1182 * Read current character and move index position.
1183 * Warning: scanner position is unchanged using this method!
1185 protected char readChar() {
1187 char c = this.source[this.index++];
1188 if (c == '\\' && this.source[this.index] == 'u') {
1190 int pos = this.index;
1192 while (this.source[this.index] == 'u')
1194 if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
1195 || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
1196 || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
1197 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1199 // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
1207 * Read token only if previous was consumed
1209 private int readToken() throws InvalidInputException {
1210 if (this.currentTokenType < 0) {
1211 this.tokenPreviousPosition = this.scanner.currentPosition;
1212 this.currentTokenType = this.scanner.getNextToken();
1213 if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1214 this.lineStarted = false;
1215 while (this.currentTokenType == TerminalTokens.TokenNameMULTIPLY) {
1216 this.currentTokenType = this.scanner.getNextToken();
1219 this.index = this.scanner.currentPosition;
1220 this.lineStarted = true; // after having read a token, line is obviously started...
1222 return this.currentTokenType;
1225 protected int readTokenAndConsume() throws InvalidInputException {
1226 int token = readToken();
1232 * Refresh start position and length of an inline tag.
1234 protected void refreshInlineTagPosition(int previousPosition) {
1235 // do nothing by default
1238 public String toString() {
1239 StringBuffer buffer = new StringBuffer();
1240 int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
1241 int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
1242 if (startPos == this.source.length)
1243 return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
1244 if (endPos > this.source.length)
1245 return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
1247 char front[] = new char[startPos];
1248 System.arraycopy(this.source, 0, front, 0, startPos);
1250 int middleLength = (endPos - 1) - startPos + 1;
1252 if (middleLength > -1) {
1253 middle = new char[middleLength];
1261 middle = CharOperation.NO_CHAR;
1264 char end[] = new char[this.source.length - (endPos - 1)];
1270 this.source.length - (endPos - 1) - 1);
1272 buffer.append(front);
1273 if (this.scanner.currentPosition<this.index) {
1274 buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
1276 buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
1278 buffer.append(middle);
1279 if (this.scanner.currentPosition<this.index) {
1280 buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
1282 buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
1286 return buffer.toString();
1292 protected abstract void updateDocComment();
1297 protected void updateLineEnd() {
1298 while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1299 if (this.linePtr < this.lastLinePtr) {
1300 this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
1302 this.lineEnd = this.endComment;
1309 * Verify that end of the line only contains space characters or end of comment.
1310 * Note that end of comment may be preceeding by several contiguous '*' chars.
1312 private boolean verifyEndLine(int textPosition) {
1313 // Special case for inline tag
1314 if (this.inlineTagStarted) {
1315 // expecting closing brace
1316 if (peekChar() == '}') {
1317 if (this.kind == DOM_PARSER) {
1319 pushText(textPosition, this.starPosition);
1326 int startPosition = this.index;
1327 int previousPosition = this.index;
1328 this.starPosition = -1;
1329 char ch = readChar();
1330 nextChar: while (true) {
1334 if (this.kind == DOM_PARSER) {
1336 pushText(textPosition, previousPosition);
1338 this.index = previousPosition;
1340 case '\u000c' : /* FORM FEED */
1341 case ' ' : /* SPACE */
1342 case '\t' : /* HORIZONTAL TABULATION */
1343 if (this.starPosition >= 0) break nextChar;
1346 this.starPosition = previousPosition;
1349 if (this.starPosition >= textPosition) {
1350 if (this.kind == DOM_PARSER) {
1352 pushText(textPosition, this.starPosition);
1361 previousPosition = this.index;
1364 this.index = startPosition;
1369 * Verify characters after a name matches one of following conditions:
1370 * 1- first character is a white space
1371 * 2- first character is a closing brace *and* we're currently parsing an inline tag
1372 * 3- are the end of comment (several contiguous star ('*') characters may be
1373 * found before the last slash ('/') character).
1375 private boolean verifySpaceOrEndComment() {
1376 int startPosition = this.index;
1377 // Whitespace or inline tag closing brace
1378 char ch = peekChar();
1381 return this.inlineTagStarted;
1383 if (Character.isWhitespace(ch)) {
1388 int previousPosition = this.index;
1389 this.starPosition = -1;
1391 nextChar: while (this.index<this.source.length) {
1394 // valid whatever the number of star before last '/'
1395 this.starPosition = previousPosition;
1398 if (this.starPosition >= startPosition) { // valid only if a star was previous character
1402 // invalid whatever other character, even white spaces
1403 this.index = startPosition;
1407 previousPosition = this.index;
1410 this.index = startPosition;