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 net.sourceforge.phpdt.internal.compiler.parser;
13 import java.util.ArrayList;
14 import java.util.List;
16 import net.sourceforge.phpdt.core.compiler.CharOperation;
17 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
18 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
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, false, false, null, null, false);
87 this.identifierStack = new char[20][];
88 this.identifierPositionStack = new long[20];
89 this.identifierLengthStack = new int[10];
90 this.astStack = new Object[30];
91 this.astLengthStack = new int[20];
95 * Returns true if tag @deprecated is present in javadoc comment.
97 * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
98 * slot for being consumed later on.
100 protected boolean parseComment(int javadocStart, int javadocEnd) {
102 boolean validComment = true;
104 // Init scanner position
105 this.scanner.resetTo(javadocStart, javadocEnd);
106 this.endComment = javadocEnd;
107 this.index = javadocStart;
108 readChar(); // starting '/'
109 int previousPosition = this.index;
110 readChar(); // first '*'
111 char nextCharacter= readChar(); // second '*'
113 // Init local variables
114 this.astLengthPtr = -1;
116 this.currentTokenType = -1;
117 this.inlineTagStarted = false;
118 this.inlineTagStart = -1;
119 this.lineStarted = false;
120 this.returnStatement = null;
121 this.inherited = false;
122 this.deprecated = false;
123 this.linePtr = getLineNumber(javadocStart);
124 this.lastLinePtr = getLineNumber(javadocEnd);
125 this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
127 char previousChar = 0;
128 int invalidTagLineEnd = -1;
129 int invalidInlineTagLineEnd = -1;
131 // Loop on each comment character
132 while (this.index < this.endComment) {
133 previousPosition = this.index;
134 previousChar = nextCharacter;
136 // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
137 if (this.index > (this.lineEnd+1)) {
141 // Read next char only if token was consumed
142 if (this.currentTokenType < 0) {
143 nextCharacter = readChar(); // consider unicodes
145 previousPosition = this.scanner.getCurrentTokenStartPosition();
146 switch (this.currentTokenType) {
147 case ITerminalSymbols.TokenNameRBRACE:
150 case ITerminalSymbols.TokenNameMULTIPLY:
154 nextCharacter = this.scanner.currentCharacter;
159 if (this.index >= this.endComment) {
163 switch (nextCharacter) {
165 boolean valid = false;
166 // Start tag parsing only if we have a java identifier start character and if we are on line beginning or at inline tag beginning
167 if ((!this.lineStarted || previousChar == '{')) {
168 this.lineStarted = true;
169 if (this.inlineTagStarted) {
170 this.inlineTagStarted = false;
171 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
172 // Cannot have @ inside inline comment
173 if (this.sourceParser != null) {
174 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
175 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
177 validComment = false;
178 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
179 pushText(this.textStart, previousPosition);
181 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
183 if (previousChar == '{') {
184 if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
185 pushText(this.textStart, this.inlineTagStart);
187 this.inlineTagStarted = true;
188 invalidInlineTagLineEnd = this.lineEnd;
189 } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
190 pushText(this.textStart, invalidTagLineEnd);
192 this.scanner.resetTo(this.index, this.endComment);
193 this.currentTokenType = -1; // flush token cache at line begin
195 int token = readTokenAndConsume();
196 this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
197 this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
198 char[] tag = this.scanner.getCurrentIdentifierSource(); // first token is either an identifier or a keyword
199 if (this.kind == DOM_PARSER) {
200 // For DOM parser, try to get tag name other than java identifier
201 // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
203 int le = this.lineEnd;
204 char pc = peekChar();
205 tagNameToken: while (tk != ITerminalSymbols.TokenNameEOF) {
206 this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
208 // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names
217 // case '-': allowed in tag names as this character is often used in doclets (bug 68087)
220 case '*': // break for '*' as this is perhaps the end of comment (bug 65288)
223 if (pc == ' ' || Character.isWhitespace(pc)) break tagNameToken;
225 tk = readTokenAndConsume();
228 int length = this.tagSourceEnd-this.tagSourceStart+1;
229 tag = new char[length];
230 System.arraycopy(this.source, this.tagSourceStart, tag, 0, length);
231 this.index = this.tagSourceEnd+1;
232 this.scanner.currentPosition = this.tagSourceEnd+1;
233 this.tagSourceStart = previousPosition;
237 case ITerminalSymbols.TokenNameIdentifier :
238 if (CharOperation.equals(tag, TAG_DEPRECATED)) {
239 this.deprecated = true;
240 if (this.kind == DOM_PARSER) {
245 } else if (CharOperation.equals(tag, TAG_INHERITDOC)) {
246 // inhibits inherited flag when tags have been already stored
247 // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
248 // Note that for DOM_PARSER, nodes stack may be not empty even no '@' tag
249 // was encountered in comment. But it cannot be the case for COMPILER_PARSER
250 // and so is enough as it is only this parser which signals the missing tag warnings...
251 this.inherited = this.astPtr==-1;
252 if (this.kind == DOM_PARSER) {
257 } else if (CharOperation.equals(tag, TAG_PARAM)) {
258 valid = parseParam();
259 } else if (CharOperation.equals(tag, TAG_EXCEPTION)) {
260 valid = parseThrows(false);
261 } else if (CharOperation.equals(tag, TAG_SEE)) {
262 if (this.inlineTagStarted) {
263 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
264 // Cannot have @see inside inline comment
266 if (this.sourceParser != null)
267 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
269 valid = parseSee(false);
271 } else if (CharOperation.equals(tag, TAG_LINK)) {
272 if (this.inlineTagStarted) {
273 valid = parseSee(false);
275 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
276 // Cannot have @link outside inline comment
278 if (this.sourceParser != null)
279 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
281 } else if (CharOperation.equals(tag, TAG_LINKPLAIN)) {
282 if (this.inlineTagStarted) {
283 valid = parseSee(true);
291 case ITerminalSymbols.TokenNamereturn :
292 valid = parseReturn();
293 // verify characters after return tag (we're expecting text description)
294 if(!verifyCharsAfterReturnTag(this.index)) {
295 if (this.sourceParser != null) {
296 int end = this.starPosition == -1 || this.lineEnd<this.starPosition ? this.lineEnd : this.starPosition;
297 this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, end);
301 // case ITerminalSymbols.TokenNamethrows :
302 // valid = parseThrows(true);
305 if (this.kind == DOM_PARSER) {
307 case ITerminalSymbols.TokenNameabstract:
308 // case ITerminalSymbols.TokenNameassert:
309 // case ITerminalSymbols.TokenNameboolean:
310 case ITerminalSymbols.TokenNamebreak:
311 // case ITerminalSymbols.TokenNamebyte:
312 case ITerminalSymbols.TokenNamecase:
313 case ITerminalSymbols.TokenNamecatch:
314 // case ITerminalSymbols.TokenNamechar:
315 case ITerminalSymbols.TokenNameclass:
316 case ITerminalSymbols.TokenNamecontinue:
317 case ITerminalSymbols.TokenNamedefault:
318 case ITerminalSymbols.TokenNamedo:
319 // case ITerminalSymbols.TokenNamedouble:
320 case ITerminalSymbols.TokenNameelse:
321 case ITerminalSymbols.TokenNameextends:
322 // case ITerminalSymbols.TokenNamefalse:
323 case ITerminalSymbols.TokenNamefinal:
324 case ITerminalSymbols.TokenNamefinally:
325 // case ITerminalSymbols.TokenNamefloat:
326 case ITerminalSymbols.TokenNamefor:
327 case ITerminalSymbols.TokenNameif:
328 case ITerminalSymbols.TokenNameimplements:
329 // case ITerminalSymbols.TokenNameimport:
330 case ITerminalSymbols.TokenNameinstanceof:
331 // case ITerminalSymbols.TokenNameint:
332 case ITerminalSymbols.TokenNameinterface:
333 // case ITerminalSymbols.TokenNamelong:
334 // case ITerminalSymbols.TokenNamenative:
335 case ITerminalSymbols.TokenNamenew:
336 // case ITerminalSymbols.TokenNamenull:
337 // case ITerminalSymbols.TokenNamepackage:
338 case ITerminalSymbols.TokenNameprivate:
339 case ITerminalSymbols.TokenNameprotected:
340 case ITerminalSymbols.TokenNamepublic:
341 // case ITerminalSymbols.TokenNameshort:
342 case ITerminalSymbols.TokenNamestatic:
343 // case ITerminalSymbols.TokenNamestrictfp:
344 case ITerminalSymbols.TokenNamesuper:
345 case ITerminalSymbols.TokenNameswitch:
346 // case ITerminalSymbols.TokenNamesynchronized:
347 // case ITerminalSymbols.TokenNamethis:
348 case ITerminalSymbols.TokenNamethrow:
349 // case ITerminalSymbols.TokenNametransient:
350 // case ITerminalSymbols.TokenNametrue:
351 case ITerminalSymbols.TokenNametry:
352 // case ITerminalSymbols.TokenNamevoid:
353 // case ITerminalSymbols.TokenNamevolatile:
354 case ITerminalSymbols.TokenNamewhile:
360 this.textStart = this.index;
362 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
363 // do not stop the inline tag when error is encountered to get text after
364 validComment = false;
365 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
366 // for DOM AST node, store tag as text in case of invalid syntax
367 if (this.kind == DOM_PARSER) {
369 this.textStart = this.tagSourceEnd+1;
370 invalidTagLineEnd = this.lineEnd;
373 } catch (InvalidInputException e) {
380 if (this.lineStarted && this.textStart < previousPosition) {
381 pushText(this.textStart, previousPosition);
383 this.lineStarted = false;
388 if (this.inlineTagStarted) {
389 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
390 pushText(this.textStart, previousPosition);
392 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
393 this.textStart = this.index;
394 this.inlineTagStarted = false;
396 if (!this.lineStarted) {
397 this.textStart = previousPosition;
400 this.lineStarted = true;
403 if (this.inlineTagStarted) {
404 this.inlineTagStarted = false;
405 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
406 // Cannot have opening brace in inline comment
407 if (this.sourceParser != null) {
408 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
409 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
411 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
412 pushText(this.textStart, previousPosition);
414 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
416 if (!this.lineStarted) {
417 this.textStart = previousPosition;
419 this.lineStarted = true;
420 this.inlineTagStart = previousPosition;
423 case '\u000c' : /* FORM FEED */
424 case ' ' : /* SPACE */
425 case '\t' : /* HORIZONTAL TABULATION */
426 // do nothing for space or '*' characters
429 if (!this.lineStarted) {
430 this.textStart = previousPosition;
432 this.lineStarted = true;
436 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
437 // Cannot leave comment inside inline comment
438 if (this.inlineTagStarted) {
439 this.inlineTagStarted = false;
440 if (this.sourceParser != null) {
441 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
442 if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
443 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
445 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
446 pushText(this.textStart, previousPosition);
448 if (this.kind == DOM_PARSER) {
449 refreshInlineTagPosition(previousPosition);
451 } else if (this.lineStarted && this.textStart < previousPosition) {
452 pushText(this.textStart, previousPosition);
455 } catch (Exception ex) {
456 validComment = false;
461 private void consumeToken() {
462 this.currentTokenType = -1; // flush token cache
466 protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
467 protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
468 protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
469 protected Object createReturnStatement() { return null; }
470 protected abstract Object createTypeReference(int primitiveToken);
472 private int getEndPosition() {
473 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
476 return this.scanner.getCurrentTokenEndPosition();
481 * Search the source position corresponding to the end of a given line number.
482 * Warning: returned position is 1-based index!
483 * @see Scanner#getLineEnd(int) We cannot directly use this method
484 * when linePtr field is not initialized.
486 private int getLineEnd(int lineNumber) {
488 if (this.scanner.linePtr != -1) {
489 return this.scanner.getLineEnd(lineNumber);
491 if (this.lineEnds == null)
493 if (lineNumber > this.lineEnds.length+1)
497 if (lineNumber == this.lineEnds.length + 1)
498 return this.scanner.eofPosition;
499 return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
504 * Search the line number corresponding to a specific position.
505 * Warning: returned position is 1-based index!
506 * @see Scanner#getLineNumber(int) We cannot directly use this method
507 * when linePtr field is not initialized.
509 private int getLineNumber(int position) {
511 if (this.scanner.linePtr != -1) {
512 return this.scanner.getLineNumber(position);
514 if (this.lineEnds == null)
516 int length = this.lineEnds.length;
519 int g = 0, d = length - 1;
523 if (position < this.lineEnds[m]) {
525 } else if (position > this.lineEnds[m]) {
531 if (position < this.lineEnds[m]) {
538 * Parse argument in @see tag method reference
540 private Object parseArguments(Object receiver) throws InvalidInputException {
543 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
545 char[] argName = null;
546 List arguments = new ArrayList(10);
547 int start = this.scanner.getCurrentTokenStartPosition();
549 // Parse arguments declaration if method reference
550 nextArg : while (this.index < this.scanner.eofPosition) {
552 // Read argument type reference
555 typeRef = parseQualifiedName(false);
556 } catch (InvalidInputException e) {
559 boolean firstArg = modulo == 0;
560 if (firstArg) { // verify position
563 } else if ((iToken % modulo) != 0) {
566 if (typeRef == null) {
567 if (firstArg && this.currentTokenType == ITerminalSymbols.TokenNameRPAREN) {
568 // verify characters after arguments declaration (expecting white space or end comment)
569 if (!verifySpaceOrEndComment()) {
570 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
571 if (this.source[end]=='\n') end--;
572 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
575 this.lineStarted = true;
576 return createMethodReference(receiver, null);
582 // Read possible array declaration
584 long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
585 if (readToken() == ITerminalSymbols.TokenNameLBRACKET) {
586 int dimStart = this.scanner.getCurrentTokenStartPosition();
587 while (readToken() == ITerminalSymbols.TokenNameLBRACKET) {
589 if (readToken() != ITerminalSymbols.TokenNameRBRACKET) {
593 dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
597 // Read argument name
598 long argNamePos = -1;
599 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
601 if (firstArg) { // verify position
604 } else if ((iToken % modulo) != 1) {
607 if (argName == null) { // verify that all arguments name are declared
612 argName = this.scanner.getCurrentIdentifierSource();
613 argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
615 } else if (argName != null) { // verify that no argument name is declared
619 // Verify token position
623 if ((iToken % modulo) != (modulo - 1)) {
628 // Read separator or end arguments declaration
629 int token = readToken();
630 char[] name = argName == null ? new char[0] : argName;
631 if (token == ITerminalSymbols.TokenNameCOMMA) {
632 // Create new argument
633 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
634 arguments.add(argument);
637 } else if (token == ITerminalSymbols.TokenNameRPAREN) {
638 // verify characters after arguments declaration (expecting white space or end comment)
639 if (!verifySpaceOrEndComment()) {
640 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
641 if (this.source[end]=='\n') end--;
642 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
645 // Create new argument
646 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
647 arguments.add(argument);
649 return createMethodReference(receiver, arguments);
655 // Something wrong happened => Invalid input
656 throw new InvalidInputException();
660 * Parse an URL link reference in @see tag
662 private boolean parseHref() throws InvalidInputException {
663 int start = this.scanner.getCurrentTokenStartPosition();
664 if (Character.toLowerCase(readChar()) == 'a') {
665 this.scanner.currentPosition = this.index;
666 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
667 this.currentTokenType = -1; // do not update line end
669 if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
670 readToken() == ITerminalSymbols.TokenNameEQUAL) {
671 this.currentTokenType = -1; // do not update line end
672 if (readToken() == ITerminalSymbols.TokenNameStringDoubleQuote ||
673 readToken() == ITerminalSymbols.TokenNameStringSingleQuote) {
674 this.currentTokenType = -1; // do not update line end
675 // Skip all characters after string literal until closing '>' (see bug 68726)
676 while (this.index <= this.lineEnd && readToken() != ITerminalSymbols.TokenNameGREATER) {
677 this.currentTokenType = -1; // do not update line end
679 if (this.currentTokenType == ITerminalSymbols.TokenNameGREATER) {
680 consumeToken(); // update line end as new lines are allowed in URL description
681 while (readToken() != ITerminalSymbols.TokenNameLESS) {
682 if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@') {
683 // Reset position: we want to rescan last token
684 this.index = this.tokenPreviousPosition;
685 this.scanner.currentPosition = this.tokenPreviousPosition;
686 this.currentTokenType = -1;
687 // Signal syntax error
688 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
693 this.currentTokenType = -1; // do not update line end
694 if (readChar() == '/') {
695 if (Character.toLowerCase(readChar()) == 'a') {
696 if (readChar() == '>') {
705 } catch (InvalidInputException ex) {
706 // Do nothing as we want to keep positions for error message
710 // Reset position: we want to rescan last token
711 this.index = this.tokenPreviousPosition;
712 this.scanner.currentPosition = this.tokenPreviousPosition;
713 this.currentTokenType = -1;
714 // Signal syntax error
715 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
720 * Parse a method reference in @see tag
722 private Object parseMember(Object receiver) throws InvalidInputException {
724 this.identifierPtr = -1;
725 this.identifierLengthPtr = -1;
726 int start = this.scanner.getCurrentTokenStartPosition();
727 this.memberStart = start;
729 // Get member identifier
730 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
732 pushIdentifier(true);
733 // Look for next token to know whether it's a field or method reference
734 int previousPosition = this.index;
735 if (readToken() == ITerminalSymbols.TokenNameLPAREN) {
737 start = this.scanner.getCurrentTokenStartPosition();
739 return parseArguments(receiver);
740 } catch (InvalidInputException e) {
741 int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
742 this.scanner.getCurrentTokenEndPosition() :
743 this.scanner.getCurrentTokenStartPosition();
744 end = end < this.lineEnd ? end : this.lineEnd;
745 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
750 // Reset position: we want to rescan last token
751 this.index = previousPosition;
752 this.scanner.currentPosition = previousPosition;
753 this.currentTokenType = -1;
755 // Verify character(s) after identifier (expecting space or end comment)
756 if (!verifySpaceOrEndComment()) {
757 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
758 if (this.source[end]=='\n') end--;
759 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
762 return createFieldReference(receiver);
764 int end = getEndPosition() - 1;
765 end = start > end ? start : end;
766 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, end);
767 // Reset position: we want to rescan last token
768 this.index = this.tokenPreviousPosition;
769 this.scanner.currentPosition = this.tokenPreviousPosition;
770 this.currentTokenType = -1;
775 * Parse @param tag declaration
777 protected boolean parseParam() {
779 // Store current token state
780 int start = this.tagSourceStart;
781 int end = this.tagSourceEnd;
784 // Push identifier next
785 int token = readToken();
787 case ITerminalSymbols.TokenNameIdentifier :
789 return pushParamName();
790 case ITerminalSymbols.TokenNameEOF :
793 start = this.scanner.getCurrentTokenStartPosition();
794 end = getEndPosition();
795 if (end < start) start = this.tagSourceStart;
798 } catch (InvalidInputException e) {
799 end = getEndPosition();
802 // Reset position to avoid missing tokens when new line was encountered
803 this.index = this.tokenPreviousPosition;
804 this.scanner.currentPosition = this.tokenPreviousPosition;
805 this.currentTokenType = -1;
808 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingParamName(start, end);
813 * Parse a qualified name and built a type reference if the syntax is valid.
815 protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
817 // Reset identifier stack if requested
819 this.identifierPtr = -1;
820 this.identifierLengthPtr = -1;
824 int primitiveToken = -1;
825 nextToken : for (int iToken = 0; ; iToken++) {
826 int token = readToken();
828 case ITerminalSymbols.TokenNameIdentifier :
829 if (((iToken % 2) > 0)) { // identifiers must be odd tokens
832 pushIdentifier(iToken == 0);
836 case ITerminalSymbols.TokenNameDOT :
837 if ((iToken % 2) == 0) { // dots must be even tokens
838 throw new InvalidInputException();
843 // case ITerminalSymbols.TokenNamevoid :
844 // case ITerminalSymbols.TokenNameboolean :
845 // case ITerminalSymbols.TokenNamebyte :
846 // case ITerminalSymbols.TokenNamechar :
847 // case ITerminalSymbols.TokenNamedouble :
848 // case ITerminalSymbols.TokenNamefloat :
849 // case ITerminalSymbols.TokenNameint :
850 // case ITerminalSymbols.TokenNamelong :
851 // case ITerminalSymbols.TokenNameshort :
853 // throw new InvalidInputException();
855 // pushIdentifier(true);
856 // primitiveToken = token;
864 if ((iToken % 2) == 0) { // cannot leave on a dot
865 // Reset position: we want to rescan last token
866 if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
867 this.index = this.tokenPreviousPosition;
868 this.scanner.currentPosition = this.tokenPreviousPosition;
869 this.currentTokenType = -1;
871 throw new InvalidInputException();
876 // Reset position: we want to rescan last token
877 if (this.currentTokenType != -1) {
878 this.index = this.tokenPreviousPosition;
879 this.scanner.currentPosition = this.tokenPreviousPosition;
880 this.currentTokenType = -1;
882 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
883 return createTypeReference(primitiveToken);
887 * Parse a reference in @see tag
889 protected boolean parseReference(boolean plain) throws InvalidInputException {
890 Object typeRef = null;
891 Object reference = null;
892 int previousPosition = -1;
893 int typeRefStartPosition = -1;
894 nextToken : while (this.index < this.scanner.eofPosition) {
895 previousPosition = this.index;
896 int token = readToken();
898 case ITerminalSymbols.TokenNameStringDoubleQuote : // @see "string"
899 case ITerminalSymbols.TokenNameStringSingleQuote :
900 int start = this.scanner.getCurrentTokenStartPosition();
902 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
903 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
904 if (typeRef != null) {
905 start = this.tagSourceEnd+1;
906 previousPosition = start;
909 // verify end line (expecting empty or end comment)
910 if (verifyEndLine(previousPosition)) {
913 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
915 case ITerminalSymbols.TokenNameLESS : // @see "<a href="URL#Value">label</a>
917 start = this.scanner.getCurrentTokenStartPosition();
920 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
921 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
922 if (typeRef != null) {
923 start = this.tagSourceEnd+1;
924 previousPosition = start;
927 // verify end line (expecting empty or end comment)
928 if (verifyEndLine(previousPosition)) {
931 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
934 case ITerminalSymbols.TokenNameERROR :
935 if (this.scanner.currentCharacter == '#') { // @see ...#member
937 reference = parseMember(typeRef);
938 if (reference != null) {
939 return pushSeeRef(reference, plain);
944 case ITerminalSymbols.TokenNameIdentifier :
945 if (typeRef == null) {
946 typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
947 typeRef = parseQualifiedName(true);
956 // Verify that we got a reference
957 if (reference == null) reference = typeRef;
958 if (reference == null) {
959 this.index = this.tokenPreviousPosition;
960 this.scanner.currentPosition = this.tokenPreviousPosition;
961 this.currentTokenType = -1;
962 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingSeeReference(this.tagSourceStart, this.tagSourceEnd);
966 // Reset position at the end of type reference
967 this.index = this.lastIdentifierEndPosition+1;
968 this.scanner.currentPosition = this.index;
969 this.currentTokenType = -1;
971 // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
972 // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
973 char ch = peekChar();
975 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(typeRefStartPosition, this.lineEnd);
979 // Verify that we get white space after reference
980 if (!verifySpaceOrEndComment()) {
981 this.index = this.tokenPreviousPosition;
982 this.scanner.currentPosition = this.tokenPreviousPosition;
983 this.currentTokenType = -1;
984 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
985 if (this.source[end]=='\n') end--;
986 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
990 // Everything is OK, store reference
991 return pushSeeRef(reference, plain);
995 * Parse @return tag declaration
997 protected abstract boolean parseReturn();
1000 * Parse @see tag declaration
1002 protected boolean parseSee(boolean plain) {
1003 int start = this.scanner.currentPosition;
1005 return parseReference(plain);
1006 } catch (InvalidInputException ex) {
1007 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, getEndPosition());
1009 // Reset position to avoid missing tokens when new line was encountered
1010 this.index = this.tokenPreviousPosition;
1011 this.scanner.currentPosition = this.tokenPreviousPosition;
1012 this.currentTokenType = -1;
1017 * Parse @return tag declaration
1019 protected abstract boolean parseTag();
1022 * Parse @throws tag declaration
1024 protected boolean parseThrows(boolean real) {
1025 int start = this.scanner.currentPosition;
1027 Object typeRef = parseQualifiedName(true);
1028 if (typeRef == null) {
1029 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd);
1031 return pushThrowName(typeRef, real);
1033 } catch (InvalidInputException ex) {
1034 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getEndPosition());
1040 * Return current character without move index position.
1042 private char peekChar() {
1043 int idx = this.index;
1044 char c = this.source[idx++];
1045 if (c == '\\' && this.source[idx] == 'u') {
1048 while (this.source[idx] == 'u')
1050 if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
1051 || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
1052 || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
1053 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1060 * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
1062 protected void pushIdentifier(boolean newLength) {
1064 int stackLength = this.identifierStack.length;
1065 if (++this.identifierPtr >= stackLength) {
1067 this.identifierStack, 0,
1068 this.identifierStack = new char[stackLength + 10][], 0,
1071 this.identifierPositionStack, 0,
1072 this.identifierPositionStack = new long[stackLength + 10], 0,
1075 this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
1076 this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
1079 stackLength = this.identifierLengthStack.length;
1080 if (++this.identifierLengthPtr >= stackLength) {
1082 this.identifierLengthStack, 0,
1083 this.identifierLengthStack = new int[stackLength + 10], 0,
1086 this.identifierLengthStack[this.identifierLengthPtr] = 1;
1088 this.identifierLengthStack[this.identifierLengthPtr]++;
1093 * Add a new obj on top of the ast stack.
1094 * If new length is required, then add also a new length in length stack.
1096 protected void pushOnAstStack(Object node, boolean newLength) {
1099 this.astLengthStack[++this.astLengthPtr] = 0;
1103 int stackLength = this.astStack.length;
1104 if (++this.astPtr >= stackLength) {
1107 this.astStack = new Object[stackLength + AstStackIncrement], 0,
1109 this.astPtr = stackLength;
1111 this.astStack[this.astPtr] = node;
1114 stackLength = this.astLengthStack.length;
1115 if (++this.astLengthPtr >= stackLength) {
1117 this.astLengthStack, 0,
1118 this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
1121 this.astLengthStack[this.astLengthPtr] = 1;
1123 this.astLengthStack[this.astLengthPtr]++;
1128 * Push a param name in ast node stack.
1130 protected abstract boolean pushParamName();
1133 * Push a reference statement in ast node stack.
1135 protected abstract boolean pushSeeRef(Object statement, boolean plain);
1138 * Push a text element in ast node stack
1140 protected abstract void pushText(int start, int end);
1143 * Push a throws type ref in ast node stack.
1145 protected abstract boolean pushThrowName(Object typeRef, boolean real);
1148 * Read current character and move index position.
1149 * Warning: scanner position is unchanged using this method!
1151 protected char readChar() {
1153 char c = this.source[this.index++];
1154 if (c == '\\' && this.source[this.index] == 'u') {
1156 int pos = this.index;
1158 while (this.source[this.index] == 'u')
1160 if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
1161 || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
1162 || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
1163 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1165 // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
1173 * Read token only if previous was consumed
1175 private int readToken() throws InvalidInputException {
1176 if (this.currentTokenType < 0) {
1177 this.tokenPreviousPosition = this.scanner.currentPosition;
1178 this.currentTokenType = this.scanner.getNextToken();
1179 if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1180 this.lineStarted = false;
1181 while (this.currentTokenType == ITerminalSymbols.TokenNameMULTIPLY) {
1182 this.currentTokenType = this.scanner.getNextToken();
1185 this.index = this.scanner.currentPosition;
1186 this.lineStarted = true; // after having read a token, line is obviously started...
1188 return this.currentTokenType;
1191 private int readTokenAndConsume() throws InvalidInputException {
1192 int token = readToken();
1198 * Refresh start position and length of an inline tag.
1200 protected void refreshInlineTagPosition(int previousPosition) {
1201 // do nothing by default
1204 public String toString() {
1205 StringBuffer buffer = new StringBuffer();
1206 int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
1207 int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
1208 if (startPos == this.source.length)
1209 return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
1210 if (endPos > this.source.length)
1211 return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
1213 char front[] = new char[startPos];
1214 System.arraycopy(this.source, 0, front, 0, startPos);
1216 int middleLength = (endPos - 1) - startPos + 1;
1218 if (middleLength > -1) {
1219 middle = new char[middleLength];
1227 middle = CharOperation.NO_CHAR;
1230 char end[] = new char[this.source.length - (endPos - 1)];
1236 this.source.length - (endPos - 1) - 1);
1238 buffer.append(front);
1239 if (this.scanner.currentPosition<this.index) {
1240 buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
1242 buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
1244 buffer.append(middle);
1245 if (this.scanner.currentPosition<this.index) {
1246 buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
1248 buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
1252 return buffer.toString();
1258 protected abstract void updateDocComment();
1263 protected void updateLineEnd() {
1264 while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1265 if (this.linePtr < this.lastLinePtr) {
1266 this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
1268 this.lineEnd = this.endComment;
1275 * Verify that end of the line only contains space characters or end of comment.
1276 * Note that end of comment may be preceeding by several contiguous '*' chars.
1278 private boolean verifyEndLine(int textPosition) {
1279 int startPosition = this.index;
1280 int previousPosition = this.index;
1281 this.starPosition = -1;
1282 char ch = readChar();
1283 nextChar: while (true) {
1287 if (this.kind == DOM_PARSER) {
1289 pushText(textPosition, previousPosition);
1291 this.index = previousPosition;
1293 case '\u000c' : /* FORM FEED */
1294 case ' ' : /* SPACE */
1295 case '\t' : /* HORIZONTAL TABULATION */
1296 if (this.starPosition >= 0) break nextChar;
1299 this.starPosition = previousPosition;
1302 if (this.starPosition >= textPosition) {
1303 if (this.kind == DOM_PARSER) {
1305 pushText(textPosition, this.starPosition);
1314 previousPosition = this.index;
1317 this.index = startPosition;
1322 * Verify that some text exists after a @return tag. Text must be different than
1323 * end of comment which may be preceeding by several '*' chars.
1325 private boolean verifyCharsAfterReturnTag(int startPosition) {
1326 // Whitespace or inline tag closing brace
1327 int previousPosition = this.index;
1328 char ch = readChar();
1329 boolean malformed = true;
1330 while (Character.isWhitespace(ch)) {
1332 previousPosition = this.index;
1336 this.starPosition = -1;
1337 nextChar: while (this.index<this.source.length) {
1340 // valid whatever the number of star before last '/'
1341 this.starPosition = previousPosition;
1344 if (this.starPosition >= startPosition) { // valid only if a star was previous character
1348 // valid if any other character is encountered, even white spaces
1349 this.index = startPosition;
1353 previousPosition = this.index;
1356 this.index = startPosition;
1361 * Verify characters after a name matches one of following conditions:
1362 * 1- first character is a white space
1363 * 2- first character is a closing brace *and* we're currently parsing an inline tag
1364 * 3- are the end of comment (several contiguous star ('*') characters may be
1365 * found before the last slash ('/') character).
1367 private boolean verifySpaceOrEndComment() {
1368 int startPosition = this.index;
1369 // Whitespace or inline tag closing brace
1370 char ch = peekChar();
1373 return this.inlineTagStarted;
1375 if (Character.isWhitespace(ch)) {
1380 int previousPosition = this.index;
1381 this.starPosition = -1;
1383 nextChar: while (this.index<this.source.length) {
1386 // valid whatever the number of star before last '/'
1387 this.starPosition = previousPosition;
1390 if (this.starPosition >= startPosition) { // valid only if a star was previous character
1394 // invalid whatever other character, even white spaces
1395 this.index = startPosition;
1399 previousPosition = this.index;
1402 this.index = startPosition;