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$
36 // tags expected positions
37 public final static int ORDERED_TAGS_NUMBER = 3;
38 public final static int PARAM_TAG_EXPECTED_ORDER = 0;
39 public final static int THROWS_TAG_EXPECTED_ORDER = 1;
40 public final static int SEE_TAG_EXPECTED_ORDER = 2;
42 // Kind of comment parser
43 public final static int COMPIL_PARSER = 0x00000001;
44 public final static int DOM_PARSER = 0x00000002;
47 public Scanner scanner;
48 public boolean checkDocComment = false;
51 protected boolean inherited, deprecated;
52 protected char[] source;
53 protected int index, endComment, lineEnd;
54 protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
55 protected int textStart, memberStart;
56 protected int tagSourceStart, tagSourceEnd;
57 protected int inlineTagStart;
58 protected Parser sourceParser;
59 protected Object returnStatement;
60 protected boolean lineStarted = false, inlineTagStarted = false;
62 protected int[] lineEnds;
65 private int currentTokenType = -1;
68 private int linePtr, lastLinePtr;
71 protected int identifierPtr;
72 protected char[][] identifierStack;
73 protected int identifierLengthPtr;
74 protected int[] identifierLengthStack;
75 protected long[] identifierPositionStack;
77 protected static int AstStackIncrement = 10;
79 protected Object[] astStack;
80 protected int astLengthPtr;
81 protected int[] astLengthStack;
83 protected AbstractCommentParser(Parser sourceParser) {
84 this.sourceParser = sourceParser;
85 this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/);
86 this.identifierStack = new char[20][];
87 this.identifierPositionStack = new long[20];
88 this.identifierLengthStack = new int[10];
89 this.astStack = new Object[30];
90 this.astLengthStack = new int[20];
94 * Returns true if tag @deprecated is present in javadoc comment.
96 * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
97 * slot for being consumed later on.
99 protected boolean parseComment(int javadocStart, int javadocEnd) {
101 boolean validComment = true;
103 // Init scanner position
104 this.scanner.resetTo(javadocStart, javadocEnd);
105 this.endComment = javadocEnd;
106 this.index = javadocStart;
107 readChar(); // starting '/'
108 int previousPosition = this.index;
109 readChar(); // first '*'
110 char nextCharacter= readChar(); // second '*'
112 // Init local variables
113 this.astLengthPtr = -1;
115 this.currentTokenType = -1;
116 this.inlineTagStarted = false;
117 this.inlineTagStart = -1;
118 this.lineStarted = false;
119 this.returnStatement = null;
120 this.inherited = false;
121 this.deprecated = false;
122 this.linePtr = getLineNumber(javadocStart);
123 this.lastLinePtr = getLineNumber(javadocEnd);
124 this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
126 char previousChar = 0;
127 int invalidTagLineEnd = -1;
128 int invalidInlineTagLineEnd = -1;
130 // Loop on each comment character
131 while (this.index < this.endComment) {
132 previousPosition = this.index;
133 previousChar = nextCharacter;
135 // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
136 if (this.index > (this.lineEnd+1)) {
140 // Read next char only if token was consumed
141 if (this.currentTokenType < 0) {
142 nextCharacter = readChar(); // consider unicodes
144 previousPosition = this.scanner.getCurrentTokenStartPosition();
145 switch (this.currentTokenType) {
146 case TerminalTokens.TokenNameRBRACE:
149 case TerminalTokens.TokenNameMULTIPLY:
153 nextCharacter = this.scanner.currentCharacter;
158 if (this.index >= this.endComment) {
162 switch (nextCharacter) {
164 boolean valid = false;
165 // Start tag parsing only if we have a java identifier start character and if we are on line beginning or at inline tag beginning
166 if ((!this.lineStarted || previousChar == '{')) {
167 this.lineStarted = true;
168 if (this.inlineTagStarted) {
169 this.inlineTagStarted = false;
170 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
171 // Cannot have @ inside inline comment
172 if (this.sourceParser != null) {
173 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
174 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
176 validComment = false;
177 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
178 pushText(this.textStart, previousPosition);
180 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
182 if (previousChar == '{') {
183 if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
184 pushText(this.textStart, this.inlineTagStart);
186 this.inlineTagStarted = true;
187 invalidInlineTagLineEnd = this.lineEnd;
188 } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
189 pushText(this.textStart, invalidTagLineEnd);
191 this.scanner.resetTo(this.index, this.endComment);
192 this.currentTokenType = -1; // flush token cache at line begin
194 int token = readTokenAndConsume();
195 this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
196 this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
197 char[] tag = this.scanner.getCurrentIdentifierSource(); // first token is either an identifier or a keyword
198 if (this.kind == DOM_PARSER) {
199 // For DOM parser, try to get tag name other than java identifier
200 // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
202 int le = this.lineEnd;
203 char pc = peekChar();
204 tagNameToken: while (tk != TerminalTokens.TokenNameEOF) {
205 this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
207 // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names
216 // case '-': allowed in tag names as this character is often used in doclets (bug 68087)
219 case '*': // break for '*' as this is perhaps the end of comment (bug 65288)
222 if (pc == ' ' || Character.isWhitespace(pc)) break tagNameToken;
224 tk = readTokenAndConsume();
227 int length = this.tagSourceEnd-this.tagSourceStart+1;
228 tag = new char[length];
229 System.arraycopy(this.source, this.tagSourceStart, tag, 0, length);
230 this.index = this.tagSourceEnd+1;
231 this.scanner.currentPosition = this.tagSourceEnd+1;
232 this.tagSourceStart = previousPosition;
236 case TerminalTokens.TokenNameIdentifier :
237 if (CharOperation.equals(tag, TAG_DEPRECATED)) {
238 this.deprecated = true;
239 if (this.kind == DOM_PARSER) {
244 } else if (CharOperation.equals(tag, TAG_INHERITDOC)) {
245 // inhibits inherited flag when tags have been already stored
246 // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
247 // Note that for DOM_PARSER, nodes stack may be not empty even no '@' tag
248 // was encountered in comment. But it cannot be the case for COMPILER_PARSER
249 // and so is enough as it is only this parser which signals the missing tag warnings...
250 this.inherited = this.astPtr==-1;
251 if (this.kind == DOM_PARSER) {
256 } else if (CharOperation.equals(tag, TAG_PARAM)) {
257 valid = parseParam();
258 } else if (CharOperation.equals(tag, TAG_EXCEPTION)) {
259 valid = parseThrows(false);
260 } else if (CharOperation.equals(tag, TAG_SEE)) {
261 if (this.inlineTagStarted) {
262 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
263 // Cannot have @see inside inline comment
265 if (this.sourceParser != null)
266 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
268 valid = parseSee(false);
270 } else if (CharOperation.equals(tag, TAG_LINK)) {
271 if (this.inlineTagStarted) {
272 valid = parseSee(false);
274 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
275 // Cannot have @link outside inline comment
277 if (this.sourceParser != null)
278 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
280 } else if (CharOperation.equals(tag, TAG_LINKPLAIN)) {
281 if (this.inlineTagStarted) {
282 valid = parseSee(true);
290 case TerminalTokens.TokenNamereturn :
291 valid = parseReturn();
292 // verify characters after return tag (we're expecting text description)
293 if(!verifyCharsAfterReturnTag(this.index)) {
294 if (this.sourceParser != null) {
295 int end = this.starPosition == -1 || this.lineEnd<this.starPosition ? this.lineEnd : this.starPosition;
296 this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, end);
300 case TerminalTokens.TokenNamethrows :
301 valid = parseThrows(true);
304 if (this.kind == DOM_PARSER) {
306 case TerminalTokens.TokenNameabstract:
307 case TerminalTokens.TokenNameassert:
308 case TerminalTokens.TokenNameboolean:
309 case TerminalTokens.TokenNamebreak:
310 case TerminalTokens.TokenNamebyte:
311 case TerminalTokens.TokenNamecase:
312 case TerminalTokens.TokenNamecatch:
313 case TerminalTokens.TokenNamechar:
314 case TerminalTokens.TokenNameclass:
315 case TerminalTokens.TokenNamecontinue:
316 case TerminalTokens.TokenNamedefault:
317 case TerminalTokens.TokenNamedo:
318 case TerminalTokens.TokenNamedouble:
319 case TerminalTokens.TokenNameelse:
320 case TerminalTokens.TokenNameextends:
321 case TerminalTokens.TokenNamefalse:
322 case TerminalTokens.TokenNamefinal:
323 case TerminalTokens.TokenNamefinally:
324 case TerminalTokens.TokenNamefloat:
325 case TerminalTokens.TokenNamefor:
326 case TerminalTokens.TokenNameif:
327 case TerminalTokens.TokenNameimplements:
328 case TerminalTokens.TokenNameimport:
329 case TerminalTokens.TokenNameinstanceof:
330 case TerminalTokens.TokenNameint:
331 case TerminalTokens.TokenNameinterface:
332 case TerminalTokens.TokenNamelong:
333 case TerminalTokens.TokenNamenative:
334 case TerminalTokens.TokenNamenew:
335 case TerminalTokens.TokenNamenull:
336 case TerminalTokens.TokenNamepackage:
337 case TerminalTokens.TokenNameprivate:
338 case TerminalTokens.TokenNameprotected:
339 case TerminalTokens.TokenNamepublic:
340 case TerminalTokens.TokenNameshort:
341 case TerminalTokens.TokenNamestatic:
342 case TerminalTokens.TokenNamestrictfp:
343 case TerminalTokens.TokenNamesuper:
344 case TerminalTokens.TokenNameswitch:
345 case TerminalTokens.TokenNamesynchronized:
346 case TerminalTokens.TokenNamethis:
347 case TerminalTokens.TokenNamethrow:
348 case TerminalTokens.TokenNametransient:
349 case TerminalTokens.TokenNametrue:
350 case TerminalTokens.TokenNametry:
351 case TerminalTokens.TokenNamevoid:
352 case TerminalTokens.TokenNamevolatile:
353 case TerminalTokens.TokenNamewhile:
359 this.textStart = this.index;
361 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
362 // do not stop the inline tag when error is encountered to get text after
363 validComment = false;
364 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
365 // for DOM AST node, store tag as text in case of invalid syntax
366 if (this.kind == DOM_PARSER) {
368 this.textStart = this.tagSourceEnd+1;
369 invalidTagLineEnd = this.lineEnd;
372 } catch (InvalidInputException e) {
379 if (this.lineStarted && this.textStart < previousPosition) {
380 pushText(this.textStart, previousPosition);
382 this.lineStarted = false;
387 if (this.inlineTagStarted) {
388 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
389 pushText(this.textStart, previousPosition);
391 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
392 this.textStart = this.index;
393 this.inlineTagStarted = false;
395 if (!this.lineStarted) {
396 this.textStart = previousPosition;
399 this.lineStarted = true;
402 if (this.inlineTagStarted) {
403 this.inlineTagStarted = false;
404 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
405 // Cannot have opening brace in inline comment
406 if (this.sourceParser != null) {
407 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
408 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
410 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
411 pushText(this.textStart, previousPosition);
413 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
415 if (!this.lineStarted) {
416 this.textStart = previousPosition;
418 this.lineStarted = true;
419 this.inlineTagStart = previousPosition;
422 case '\u000c' : /* FORM FEED */
423 case ' ' : /* SPACE */
424 case '\t' : /* HORIZONTAL TABULATION */
425 // do nothing for space or '*' characters
428 if (!this.lineStarted) {
429 this.textStart = previousPosition;
431 this.lineStarted = true;
435 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
436 // Cannot leave comment inside inline comment
437 if (this.inlineTagStarted) {
438 this.inlineTagStarted = false;
439 if (this.sourceParser != null) {
440 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
441 if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
442 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
444 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
445 pushText(this.textStart, previousPosition);
447 if (this.kind == DOM_PARSER) {
448 refreshInlineTagPosition(previousPosition);
450 } else if (this.lineStarted && this.textStart < previousPosition) {
451 pushText(this.textStart, previousPosition);
454 } catch (Exception ex) {
455 validComment = false;
460 private void consumeToken() {
461 this.currentTokenType = -1; // flush token cache
465 protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
466 protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
467 protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
468 protected Object createReturnStatement() { return null; }
469 protected abstract Object createTypeReference(int primitiveToken);
471 private int getEndPosition() {
472 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
475 return this.scanner.getCurrentTokenEndPosition();
480 * Search the source position corresponding to the end of a given line number.
481 * Warning: returned position is 1-based index!
482 * @see Scanner#getLineEnd(int) We cannot directly use this method
483 * when linePtr field is not initialized.
485 private int getLineEnd(int lineNumber) {
487 if (this.scanner.linePtr != -1) {
488 return this.scanner.getLineEnd(lineNumber);
490 if (this.lineEnds == null)
492 if (lineNumber > this.lineEnds.length+1)
496 if (lineNumber == this.lineEnds.length + 1)
497 return this.scanner.eofPosition;
498 return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
503 * Search the line number corresponding to a specific position.
504 * Warning: returned position is 1-based index!
505 * @see Scanner#getLineNumber(int) We cannot directly use this method
506 * when linePtr field is not initialized.
508 private int getLineNumber(int position) {
510 if (this.scanner.linePtr != -1) {
511 return this.scanner.getLineNumber(position);
513 if (this.lineEnds == null)
515 int length = this.lineEnds.length;
518 int g = 0, d = length - 1;
522 if (position < this.lineEnds[m]) {
524 } else if (position > this.lineEnds[m]) {
530 if (position < this.lineEnds[m]) {
537 * Parse argument in @see tag method reference
539 private Object parseArguments(Object receiver) throws InvalidInputException {
542 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
544 char[] argName = null;
545 List arguments = new ArrayList(10);
546 int start = this.scanner.getCurrentTokenStartPosition();
548 // Parse arguments declaration if method reference
549 nextArg : while (this.index < this.scanner.eofPosition) {
551 // Read argument type reference
554 typeRef = parseQualifiedName(false);
555 } catch (InvalidInputException e) {
558 boolean firstArg = modulo == 0;
559 if (firstArg) { // verify position
562 } else if ((iToken % modulo) != 0) {
565 if (typeRef == null) {
566 if (firstArg && this.currentTokenType == TerminalTokens.TokenNameRPAREN) {
567 // verify characters after arguments declaration (expecting white space or end comment)
568 if (!verifySpaceOrEndComment()) {
569 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
570 if (this.source[end]=='\n') end--;
571 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
574 this.lineStarted = true;
575 return createMethodReference(receiver, null);
581 // Read possible array declaration
583 long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
584 if (readToken() == TerminalTokens.TokenNameLBRACKET) {
585 int dimStart = this.scanner.getCurrentTokenStartPosition();
586 while (readToken() == TerminalTokens.TokenNameLBRACKET) {
588 if (readToken() != TerminalTokens.TokenNameRBRACKET) {
592 dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
596 // Read argument name
597 long argNamePos = -1;
598 if (readToken() == TerminalTokens.TokenNameIdentifier) {
600 if (firstArg) { // verify position
603 } else if ((iToken % modulo) != 1) {
606 if (argName == null) { // verify that all arguments name are declared
611 argName = this.scanner.getCurrentIdentifierSource();
612 argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
614 } else if (argName != null) { // verify that no argument name is declared
618 // Verify token position
622 if ((iToken % modulo) != (modulo - 1)) {
627 // Read separator or end arguments declaration
628 int token = readToken();
629 char[] name = argName == null ? new char[0] : argName;
630 if (token == TerminalTokens.TokenNameCOMMA) {
631 // Create new argument
632 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
633 arguments.add(argument);
636 } else if (token == TerminalTokens.TokenNameRPAREN) {
637 // verify characters after arguments declaration (expecting white space or end comment)
638 if (!verifySpaceOrEndComment()) {
639 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
640 if (this.source[end]=='\n') end--;
641 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
644 // Create new argument
645 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
646 arguments.add(argument);
648 return createMethodReference(receiver, arguments);
654 // Something wrong happened => Invalid input
655 throw new InvalidInputException();
659 * Parse an URL link reference in @see tag
661 private boolean parseHref() throws InvalidInputException {
662 int start = this.scanner.getCurrentTokenStartPosition();
663 if (Character.toLowerCase(readChar()) == 'a') {
664 this.scanner.currentPosition = this.index;
665 if (readToken() == TerminalTokens.TokenNameIdentifier) {
666 this.currentTokenType = -1; // do not update line end
668 if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
669 readToken() == TerminalTokens.TokenNameEQUAL) {
670 this.currentTokenType = -1; // do not update line end
671 if (readToken() == TerminalTokens.TokenNameStringLiteral) {
672 this.currentTokenType = -1; // do not update line end
673 // Skip all characters after string literal until closing '>' (see bug 68726)
674 while (this.index <= this.lineEnd && readToken() != TerminalTokens.TokenNameGREATER) {
675 this.currentTokenType = -1; // do not update line end
677 if (this.currentTokenType == TerminalTokens.TokenNameGREATER) {
678 consumeToken(); // update line end as new lines are allowed in URL description
679 while (readToken() != TerminalTokens.TokenNameLESS) {
680 if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@') {
681 // Reset position: we want to rescan last token
682 this.index = this.tokenPreviousPosition;
683 this.scanner.currentPosition = this.tokenPreviousPosition;
684 this.currentTokenType = -1;
685 // Signal syntax error
686 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
691 this.currentTokenType = -1; // do not update line end
692 if (readChar() == '/') {
693 if (Character.toLowerCase(readChar()) == 'a') {
694 if (readChar() == '>') {
703 } catch (InvalidInputException ex) {
704 // Do nothing as we want to keep positions for error message
708 // Reset position: we want to rescan last token
709 this.index = this.tokenPreviousPosition;
710 this.scanner.currentPosition = this.tokenPreviousPosition;
711 this.currentTokenType = -1;
712 // Signal syntax error
713 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
718 * Parse a method reference in @see tag
720 private Object parseMember(Object receiver) throws InvalidInputException {
722 this.identifierPtr = -1;
723 this.identifierLengthPtr = -1;
724 int start = this.scanner.getCurrentTokenStartPosition();
725 this.memberStart = start;
727 // Get member identifier
728 if (readToken() == TerminalTokens.TokenNameIdentifier) {
730 pushIdentifier(true);
731 // Look for next token to know whether it's a field or method reference
732 int previousPosition = this.index;
733 if (readToken() == TerminalTokens.TokenNameLPAREN) {
735 start = this.scanner.getCurrentTokenStartPosition();
737 return parseArguments(receiver);
738 } catch (InvalidInputException e) {
739 int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
740 this.scanner.getCurrentTokenEndPosition() :
741 this.scanner.getCurrentTokenStartPosition();
742 end = end < this.lineEnd ? end : this.lineEnd;
743 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
748 // Reset position: we want to rescan last token
749 this.index = previousPosition;
750 this.scanner.currentPosition = previousPosition;
751 this.currentTokenType = -1;
753 // Verify character(s) after identifier (expecting space or end comment)
754 if (!verifySpaceOrEndComment()) {
755 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
756 if (this.source[end]=='\n') end--;
757 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
760 return createFieldReference(receiver);
762 int end = getEndPosition() - 1;
763 end = start > end ? start : end;
764 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, end);
765 // Reset position: we want to rescan last token
766 this.index = this.tokenPreviousPosition;
767 this.scanner.currentPosition = this.tokenPreviousPosition;
768 this.currentTokenType = -1;
773 * Parse @param tag declaration
775 protected boolean parseParam() {
777 // Store current token state
778 int start = this.tagSourceStart;
779 int end = this.tagSourceEnd;
782 // Push identifier next
783 int token = readToken();
785 case TerminalTokens.TokenNameIdentifier :
787 return pushParamName();
788 case TerminalTokens.TokenNameEOF :
791 start = this.scanner.getCurrentTokenStartPosition();
792 end = getEndPosition();
793 if (end < start) start = this.tagSourceStart;
796 } catch (InvalidInputException e) {
797 end = getEndPosition();
800 // Reset position to avoid missing tokens when new line was encountered
801 this.index = this.tokenPreviousPosition;
802 this.scanner.currentPosition = this.tokenPreviousPosition;
803 this.currentTokenType = -1;
806 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingParamName(start, end);
811 * Parse a qualified name and built a type reference if the syntax is valid.
813 protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
815 // Reset identifier stack if requested
817 this.identifierPtr = -1;
818 this.identifierLengthPtr = -1;
822 int primitiveToken = -1;
823 nextToken : for (int iToken = 0; ; iToken++) {
824 int token = readToken();
826 case TerminalTokens.TokenNameIdentifier :
827 if (((iToken % 2) > 0)) { // identifiers must be odd tokens
830 pushIdentifier(iToken == 0);
834 case TerminalTokens.TokenNameDOT :
835 if ((iToken % 2) == 0) { // dots must be even tokens
836 throw new InvalidInputException();
841 case TerminalTokens.TokenNamevoid :
842 case TerminalTokens.TokenNameboolean :
843 case TerminalTokens.TokenNamebyte :
844 case TerminalTokens.TokenNamechar :
845 case TerminalTokens.TokenNamedouble :
846 case TerminalTokens.TokenNamefloat :
847 case TerminalTokens.TokenNameint :
848 case TerminalTokens.TokenNamelong :
849 case TerminalTokens.TokenNameshort :
851 throw new InvalidInputException();
853 pushIdentifier(true);
854 primitiveToken = token;
862 if ((iToken % 2) == 0) { // cannot leave on a dot
863 // Reset position: we want to rescan last token
864 if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
865 this.index = this.tokenPreviousPosition;
866 this.scanner.currentPosition = this.tokenPreviousPosition;
867 this.currentTokenType = -1;
869 throw new InvalidInputException();
874 // Reset position: we want to rescan last token
875 if (this.currentTokenType != -1) {
876 this.index = this.tokenPreviousPosition;
877 this.scanner.currentPosition = this.tokenPreviousPosition;
878 this.currentTokenType = -1;
880 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
881 return createTypeReference(primitiveToken);
885 * Parse a reference in @see tag
887 protected boolean parseReference(boolean plain) throws InvalidInputException {
888 Object typeRef = null;
889 Object reference = null;
890 int previousPosition = -1;
891 int typeRefStartPosition = -1;
892 nextToken : while (this.index < this.scanner.eofPosition) {
893 previousPosition = this.index;
894 int token = readToken();
896 case TerminalTokens.TokenNameStringLiteral : // @see "string"
897 int start = this.scanner.getCurrentTokenStartPosition();
899 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
900 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
901 if (typeRef != null) {
902 start = this.tagSourceEnd+1;
903 previousPosition = start;
906 // verify end line (expecting empty or end comment)
907 if (verifyEndLine(previousPosition)) {
910 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
912 case TerminalTokens.TokenNameLESS : // @see "<a href="URL#Value">label</a>
914 start = this.scanner.getCurrentTokenStartPosition();
917 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
918 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
919 if (typeRef != null) {
920 start = this.tagSourceEnd+1;
921 previousPosition = start;
924 // verify end line (expecting empty or end comment)
925 if (verifyEndLine(previousPosition)) {
928 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
931 case TerminalTokens.TokenNameERROR :
932 if (this.scanner.currentCharacter == '#') { // @see ...#member
934 reference = parseMember(typeRef);
935 if (reference != null) {
936 return pushSeeRef(reference, plain);
941 case TerminalTokens.TokenNameIdentifier :
942 if (typeRef == null) {
943 typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
944 typeRef = parseQualifiedName(true);
953 // Verify that we got a reference
954 if (reference == null) reference = typeRef;
955 if (reference == null) {
956 this.index = this.tokenPreviousPosition;
957 this.scanner.currentPosition = this.tokenPreviousPosition;
958 this.currentTokenType = -1;
959 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingSeeReference(this.tagSourceStart, this.tagSourceEnd);
963 // Reset position at the end of type reference
964 this.index = this.lastIdentifierEndPosition+1;
965 this.scanner.currentPosition = this.index;
966 this.currentTokenType = -1;
968 // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
969 // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
970 char ch = peekChar();
972 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(typeRefStartPosition, this.lineEnd);
976 // Verify that we get white space after reference
977 if (!verifySpaceOrEndComment()) {
978 this.index = this.tokenPreviousPosition;
979 this.scanner.currentPosition = this.tokenPreviousPosition;
980 this.currentTokenType = -1;
981 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
982 if (this.source[end]=='\n') end--;
983 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
987 // Everything is OK, store reference
988 return pushSeeRef(reference, plain);
992 * Parse @return tag declaration
994 protected abstract boolean parseReturn();
997 * Parse @see tag declaration
999 protected boolean parseSee(boolean plain) {
1000 int start = this.scanner.currentPosition;
1002 return parseReference(plain);
1003 } catch (InvalidInputException ex) {
1004 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, getEndPosition());
1006 // Reset position to avoid missing tokens when new line was encountered
1007 this.index = this.tokenPreviousPosition;
1008 this.scanner.currentPosition = this.tokenPreviousPosition;
1009 this.currentTokenType = -1;
1014 * Parse @return tag declaration
1016 protected abstract boolean parseTag();
1019 * Parse @throws tag declaration
1021 protected boolean parseThrows(boolean real) {
1022 int start = this.scanner.currentPosition;
1024 Object typeRef = parseQualifiedName(true);
1025 if (typeRef == null) {
1026 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd);
1028 return pushThrowName(typeRef, real);
1030 } catch (InvalidInputException ex) {
1031 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getEndPosition());
1037 * Return current character without move index position.
1039 private char peekChar() {
1040 int idx = this.index;
1041 char c = this.source[idx++];
1042 if (c == '\\' && this.source[idx] == 'u') {
1045 while (this.source[idx] == 'u')
1047 if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
1048 || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
1049 || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
1050 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1057 * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
1059 protected void pushIdentifier(boolean newLength) {
1061 int stackLength = this.identifierStack.length;
1062 if (++this.identifierPtr >= stackLength) {
1064 this.identifierStack, 0,
1065 this.identifierStack = new char[stackLength + 10][], 0,
1068 this.identifierPositionStack, 0,
1069 this.identifierPositionStack = new long[stackLength + 10], 0,
1072 this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
1073 this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
1076 stackLength = this.identifierLengthStack.length;
1077 if (++this.identifierLengthPtr >= stackLength) {
1079 this.identifierLengthStack, 0,
1080 this.identifierLengthStack = new int[stackLength + 10], 0,
1083 this.identifierLengthStack[this.identifierLengthPtr] = 1;
1085 this.identifierLengthStack[this.identifierLengthPtr]++;
1090 * Add a new obj on top of the ast stack.
1091 * If new length is required, then add also a new length in length stack.
1093 protected void pushOnAstStack(Object node, boolean newLength) {
1096 this.astLengthStack[++this.astLengthPtr] = 0;
1100 int stackLength = this.astStack.length;
1101 if (++this.astPtr >= stackLength) {
1104 this.astStack = new Object[stackLength + AstStackIncrement], 0,
1106 this.astPtr = stackLength;
1108 this.astStack[this.astPtr] = node;
1111 stackLength = this.astLengthStack.length;
1112 if (++this.astLengthPtr >= stackLength) {
1114 this.astLengthStack, 0,
1115 this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
1118 this.astLengthStack[this.astLengthPtr] = 1;
1120 this.astLengthStack[this.astLengthPtr]++;
1125 * Push a param name in ast node stack.
1127 protected abstract boolean pushParamName();
1130 * Push a reference statement in ast node stack.
1132 protected abstract boolean pushSeeRef(Object statement, boolean plain);
1135 * Push a text element in ast node stack
1137 protected abstract void pushText(int start, int end);
1140 * Push a throws type ref in ast node stack.
1142 protected abstract boolean pushThrowName(Object typeRef, boolean real);
1145 * Read current character and move index position.
1146 * Warning: scanner position is unchanged using this method!
1148 protected char readChar() {
1150 char c = this.source[this.index++];
1151 if (c == '\\' && this.source[this.index] == 'u') {
1153 int pos = this.index;
1155 while (this.source[this.index] == 'u')
1157 if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
1158 || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
1159 || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
1160 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1162 // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
1170 * Read token only if previous was consumed
1172 private int readToken() throws InvalidInputException {
1173 if (this.currentTokenType < 0) {
1174 this.tokenPreviousPosition = this.scanner.currentPosition;
1175 this.currentTokenType = this.scanner.getNextToken();
1176 if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1177 this.lineStarted = false;
1178 while (this.currentTokenType == TerminalTokens.TokenNameMULTIPLY) {
1179 this.currentTokenType = this.scanner.getNextToken();
1182 this.index = this.scanner.currentPosition;
1183 this.lineStarted = true; // after having read a token, line is obviously started...
1185 return this.currentTokenType;
1188 private int readTokenAndConsume() throws InvalidInputException {
1189 int token = readToken();
1195 * Refresh start position and length of an inline tag.
1197 protected void refreshInlineTagPosition(int previousPosition) {
1198 // do nothing by default
1201 public String toString() {
1202 StringBuffer buffer = new StringBuffer();
1203 int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
1204 int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
1205 if (startPos == this.source.length)
1206 return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
1207 if (endPos > this.source.length)
1208 return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
1210 char front[] = new char[startPos];
1211 System.arraycopy(this.source, 0, front, 0, startPos);
1213 int middleLength = (endPos - 1) - startPos + 1;
1215 if (middleLength > -1) {
1216 middle = new char[middleLength];
1224 middle = CharOperation.NO_CHAR;
1227 char end[] = new char[this.source.length - (endPos - 1)];
1233 this.source.length - (endPos - 1) - 1);
1235 buffer.append(front);
1236 if (this.scanner.currentPosition<this.index) {
1237 buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
1239 buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
1241 buffer.append(middle);
1242 if (this.scanner.currentPosition<this.index) {
1243 buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
1245 buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
1249 return buffer.toString();
1255 protected abstract void updateDocComment();
1260 protected void updateLineEnd() {
1261 while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1262 if (this.linePtr < this.lastLinePtr) {
1263 this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
1265 this.lineEnd = this.endComment;
1272 * Verify that end of the line only contains space characters or end of comment.
1273 * Note that end of comment may be preceeding by several contiguous '*' chars.
1275 private boolean verifyEndLine(int textPosition) {
1276 int startPosition = this.index;
1277 int previousPosition = this.index;
1278 this.starPosition = -1;
1279 char ch = readChar();
1280 nextChar: while (true) {
1284 if (this.kind == DOM_PARSER) {
1286 pushText(textPosition, previousPosition);
1288 this.index = previousPosition;
1290 case '\u000c' : /* FORM FEED */
1291 case ' ' : /* SPACE */
1292 case '\t' : /* HORIZONTAL TABULATION */
1293 if (this.starPosition >= 0) break nextChar;
1296 this.starPosition = previousPosition;
1299 if (this.starPosition >= textPosition) {
1300 if (this.kind == DOM_PARSER) {
1302 pushText(textPosition, this.starPosition);
1311 previousPosition = this.index;
1314 this.index = startPosition;
1319 * Verify that some text exists after a @return tag. Text must be different than
1320 * end of comment which may be preceeding by several '*' chars.
1322 private boolean verifyCharsAfterReturnTag(int startPosition) {
1323 // Whitespace or inline tag closing brace
1324 int previousPosition = this.index;
1325 char ch = readChar();
1326 boolean malformed = true;
1327 while (Character.isWhitespace(ch)) {
1329 previousPosition = this.index;
1333 this.starPosition = -1;
1334 nextChar: while (this.index<this.source.length) {
1337 // valid whatever the number of star before last '/'
1338 this.starPosition = previousPosition;
1341 if (this.starPosition >= startPosition) { // valid only if a star was previous character
1345 // valid if any other character is encountered, even white spaces
1346 this.index = startPosition;
1350 previousPosition = this.index;
1353 this.index = startPosition;
1358 * Verify characters after a name matches one of following conditions:
1359 * 1- first character is a white space
1360 * 2- first character is a closing brace *and* we're currently parsing an inline tag
1361 * 3- are the end of comment (several contiguous star ('*') characters may be
1362 * found before the last slash ('/') character).
1364 private boolean verifySpaceOrEndComment() {
1365 int startPosition = this.index;
1366 // Whitespace or inline tag closing brace
1367 char ch = peekChar();
1370 return this.inlineTagStarted;
1372 if (Character.isWhitespace(ch)) {
1377 int previousPosition = this.index;
1378 this.starPosition = -1;
1380 nextChar: while (this.index<this.source.length) {
1383 // valid whatever the number of star before last '/'
1384 this.starPosition = previousPosition;
1387 if (this.starPosition >= startPosition) { // valid only if a star was previous character
1391 // invalid whatever other character, even white spaces
1392 this.index = startPosition;
1396 previousPosition = this.index;
1399 this.index = startPosition;