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.ITerminalSymbols.TokenName;
19 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
22 * Parser specialized for decoding javadoc comments
24 public abstract class AbstractCommentParser {
27 public static final char[] TAG_DEPRECATED = "deprecated".toCharArray(); //$NON-NLS-1$
29 public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
31 public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
33 public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
35 public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
37 public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
39 public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
41 public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
43 public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
45 // tags expected positions
46 public final static int ORDERED_TAGS_NUMBER = 3;
48 public final static int PARAM_TAG_EXPECTED_ORDER = 0;
50 public final static int THROWS_TAG_EXPECTED_ORDER = 1;
52 public final static int SEE_TAG_EXPECTED_ORDER = 2;
54 // Kind of comment parser
55 public final static int COMPIL_PARSER = 0x00000001;
57 public final static int DOM_PARSER = 0x00000002;
60 public Scanner scanner;
62 public boolean checkDocComment = false;
65 protected boolean inherited, deprecated;
67 protected char[] source;
69 protected int index, endComment, lineEnd;
71 protected int tokenPreviousPosition, lastIdentifierEndPosition,
74 protected int textStart, memberStart;
76 protected int tagSourceStart, tagSourceEnd;
78 protected int inlineTagStart;
80 protected Parser sourceParser;
82 protected Object returnStatement;
84 protected boolean lineStarted = false, inlineTagStarted = false;
88 protected int[] lineEnds;
91 private TokenName currentTokenType = ITerminalSymbols.TokenName.NONE;
94 private int linePtr, lastLinePtr;
97 protected int identifierPtr;
99 protected char[][] identifierStack;
101 protected int identifierLengthPtr;
103 protected int[] identifierLengthStack;
105 protected long[] identifierPositionStack;
108 protected static int AstStackIncrement = 10;
110 protected int astPtr;
112 protected Object[] astStack;
114 protected int astLengthPtr;
116 protected int[] astLengthStack;
118 protected AbstractCommentParser(Parser sourceParser) {
119 this.sourceParser = sourceParser;
120 this.scanner = new Scanner(false, false, false, false, false, null,
123 this.identifierStack = new char[20][];
124 this.identifierPositionStack = new long[20];
125 this.identifierLengthStack = new int[10];
126 this.astStack = new Object[30];
127 this.astLengthStack = new int[20];
131 * (non-Javadoc) Returns true if tag
133 * @deprecated is present in javadoc comment.
135 * If javadoc checking is enabled, will also construct an Javadoc node,
136 * which will be stored into Parser.javadoc slot for being consumed later
139 protected boolean parseComment(int javadocStart, int javadocEnd) {
141 boolean validComment = true;
143 // Init scanner position
144 this.scanner.resetTo(javadocStart, javadocEnd);
145 this.endComment = javadocEnd;
146 this.index = javadocStart;
147 readChar(); // starting '/'
148 int previousPosition = this.index;
149 readChar(); // first '*'
150 char nextCharacter = readChar(); // second '*'
152 // Init local variables
153 this.astLengthPtr = -1;
155 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
156 this.inlineTagStarted = false;
157 this.inlineTagStart = -1;
158 this.lineStarted = false;
159 this.returnStatement = null;
160 this.inherited = false;
161 this.deprecated = false;
162 this.linePtr = getLineNumber(javadocStart);
163 this.lastLinePtr = getLineNumber(javadocEnd);
164 this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment
165 : this.scanner.getLineEnd(this.linePtr);
167 char previousChar = 0;
168 int invalidTagLineEnd = -1;
169 int invalidInlineTagLineEnd = -1;
171 // Loop on each comment character
172 while (this.index < this.endComment) {
173 previousPosition = this.index;
174 previousChar = nextCharacter;
176 // Calculate line end (cannot use this.scanner.linePtr as
177 // scanner does not parse line ends again)
178 if (this.index > (this.lineEnd + 1)) {
182 // Read next char only if token was consumed
183 if (this.currentTokenType.compareTo (ITerminalSymbols.TokenName.NONE) <= 0) {
184 nextCharacter = readChar(); // consider unicodes
186 previousPosition = this.scanner
187 .getCurrentTokenStartPosition();
188 switch (this.currentTokenType) {
196 nextCharacter = this.scanner.currentCharacter;
201 if (this.index >= this.endComment) {
205 switch (nextCharacter) {
207 boolean valid = false;
208 // Start tag parsing only if we have a java identifier start
209 // character and if we are on line beginning or at inline
211 if ((!this.lineStarted || previousChar == '{')) {
212 this.lineStarted = true;
213 if (this.inlineTagStarted) {
214 this.inlineTagStarted = false;
216 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
217 // Cannot have @ inside inline comment
218 if (this.sourceParser != null) {
219 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
220 : invalidInlineTagLineEnd;
221 this.sourceParser.problemReporter()
222 .javadocUnterminatedInlineTag(
223 this.inlineTagStart, end);
225 validComment = false;
226 if (this.lineStarted && this.textStart != -1
227 && this.textStart < previousPosition) {
228 pushText(this.textStart, previousPosition);
230 if (this.kind == DOM_PARSER)
231 refreshInlineTagPosition(previousPosition);
233 if (previousChar == '{') {
234 if (this.textStart != -1
235 && this.textStart < this.inlineTagStart) {
236 pushText(this.textStart, this.inlineTagStart);
238 this.inlineTagStarted = true;
239 invalidInlineTagLineEnd = this.lineEnd;
240 } else if (this.textStart != -1
241 && this.textStart < invalidTagLineEnd) {
242 pushText(this.textStart, invalidTagLineEnd);
244 this.scanner.resetTo(this.index, this.endComment);
245 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // flush token cache at line
248 TokenName token = readTokenAndConsume();
249 this.tagSourceStart = this.scanner
250 .getCurrentTokenStartPosition();
251 this.tagSourceEnd = this.scanner
252 .getCurrentTokenEndPosition();
253 char[] tag = this.scanner
254 .getCurrentIdentifierSource(); // first
260 if (this.kind == DOM_PARSER) {
261 // For DOM parser, try to get tag name other
262 // than java identifier
264 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
265 TokenName tk = token;
266 int le = this.lineEnd;
267 char pc = peekChar();
268 tagNameToken: while (tk != ITerminalSymbols.TokenName.EOF) {
269 this.tagSourceEnd = this.scanner
270 .getCurrentTokenEndPosition();
272 // !, ", #, %, &, ', -, :, <, >, * chars and
273 // spaces are not allowed in tag names
282 // case '-': allowed in tag names as
283 // this character is often used in
284 // doclets (bug 68087)
287 case '*': // break for '*' as this is
288 // perhaps the end of comment
293 || Character.isWhitespace(pc))
296 tk = readTokenAndConsume();
299 int length = this.tagSourceEnd
300 - this.tagSourceStart + 1;
301 tag = new char[length];
302 System.arraycopy(this.source,
303 this.tagSourceStart, tag, 0, length);
304 this.index = this.tagSourceEnd + 1;
305 this.scanner.currentPosition = this.tagSourceEnd + 1;
306 this.tagSourceStart = previousPosition;
311 if (CharOperation.equals(tag, TAG_DEPRECATED)) {
312 this.deprecated = true;
313 if (this.kind == DOM_PARSER) {
318 } else if (CharOperation.equals(tag,
320 // inhibits inherited flag when tags have
321 // been already stored
323 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
324 // Note that for DOM_PARSER, nodes stack may
325 // be not empty even no '@' tag
326 // was encountered in comment. But it cannot
327 // be the case for COMPILER_PARSER
328 // and so is enough as it is only this
329 // parser which signals the missing tag
331 this.inherited = this.astPtr == -1;
332 if (this.kind == DOM_PARSER) {
337 } else if (CharOperation.equals(tag, TAG_PARAM)) {
338 valid = parseParam();
339 } else if (CharOperation.equals(tag,
341 valid = parseThrows(false);
342 } else if (CharOperation.equals(tag, TAG_SEE)) {
343 if (this.inlineTagStarted) {
345 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
346 // Cannot have @see inside inline
349 if (this.sourceParser != null)
352 .javadocUnexpectedTag(
356 valid = parseSee(false);
358 } else if (CharOperation.equals(tag, TAG_LINK)) {
359 if (this.inlineTagStarted) {
360 valid = parseSee(false);
363 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
364 // Cannot have @link outside inline
367 if (this.sourceParser != null)
370 .javadocUnexpectedTag(
374 } else if (CharOperation.equals(tag,
376 if (this.inlineTagStarted) {
377 valid = parseSee(true);
386 valid = parseReturn();
387 // verify characters after return tag (we're
388 // expecting text description)
389 if (!verifyCharsAfterReturnTag(this.index)) {
390 if (this.sourceParser != null) {
391 int end = this.starPosition == -1
392 || this.lineEnd < this.starPosition ? this.lineEnd
394 this.sourceParser.problemReporter()
401 // case ITerminalSymbols.TokenName.throws :
402 // valid = parseThrows(true);
405 if (this.kind == DOM_PARSER) {
409 // ITerminalSymbols.TokenName.assert:
411 // ITerminalSymbols.TokenName.boolean:
470 this.textStart = this.index;
473 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
474 // do not stop the inline tag when error is
475 // encountered to get text after
476 validComment = false;
478 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
479 // for DOM AST node, store tag as text in case
481 if (this.kind == DOM_PARSER) {
483 this.textStart = this.tagSourceEnd + 1;
484 invalidTagLineEnd = this.lineEnd;
487 } catch (InvalidInputException e) {
494 if (this.lineStarted && this.textStart < previousPosition) {
495 pushText(this.textStart, previousPosition);
497 this.lineStarted = false;
502 if (this.inlineTagStarted) {
503 if (this.lineStarted && this.textStart != -1
504 && this.textStart < previousPosition) {
505 pushText(this.textStart, previousPosition);
507 if (this.kind == DOM_PARSER)
508 refreshInlineTagPosition(previousPosition);
509 this.textStart = this.index;
510 this.inlineTagStarted = false;
512 if (!this.lineStarted) {
513 this.textStart = previousPosition;
516 this.lineStarted = true;
519 if (this.inlineTagStarted) {
520 this.inlineTagStarted = false;
522 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
523 // Cannot have opening brace in inline comment
524 if (this.sourceParser != null) {
525 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
526 : invalidInlineTagLineEnd;
527 this.sourceParser.problemReporter()
528 .javadocUnterminatedInlineTag(
529 this.inlineTagStart, end);
531 if (this.lineStarted && this.textStart != -1
532 && this.textStart < previousPosition) {
533 pushText(this.textStart, previousPosition);
535 if (this.kind == DOM_PARSER)
536 refreshInlineTagPosition(previousPosition);
538 if (!this.lineStarted) {
539 this.textStart = previousPosition;
541 this.lineStarted = true;
542 this.inlineTagStart = previousPosition;
545 case '\u000c': /* FORM FEED */
546 case ' ': /* SPACE */
547 case '\t': /* HORIZONTAL TABULATION */
548 // do nothing for space or '*' characters
551 if (!this.lineStarted) {
552 this.textStart = previousPosition;
554 this.lineStarted = true;
558 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
559 // Cannot leave comment inside inline comment
560 if (this.inlineTagStarted) {
561 this.inlineTagStarted = false;
562 if (this.sourceParser != null) {
563 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
564 : invalidInlineTagLineEnd;
565 if (this.index >= this.endComment)
566 end = invalidInlineTagLineEnd;
567 this.sourceParser.problemReporter()
568 .javadocUnterminatedInlineTag(this.inlineTagStart,
571 if (this.lineStarted && this.textStart != -1
572 && this.textStart < previousPosition) {
573 pushText(this.textStart, previousPosition);
575 if (this.kind == DOM_PARSER) {
576 refreshInlineTagPosition(previousPosition);
578 } else if (this.lineStarted && this.textStart < previousPosition) {
579 pushText(this.textStart, previousPosition);
582 } catch (Exception ex) {
583 validComment = false;
588 private void consumeToken() {
589 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // flush token cache
593 protected abstract Object createArgumentReference(char[] name, int dim,
594 Object typeRef, long[] dimPos, long argNamePos)
595 throws InvalidInputException;
597 protected abstract Object createFieldReference(Object receiver)
598 throws InvalidInputException;
600 protected abstract Object createMethodReference(Object receiver,
601 List arguments) throws InvalidInputException;
603 protected Object createReturnStatement() {
607 protected abstract Object createTypeReference(int primitiveToken);
609 private int getEndPosition() {
610 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
613 return this.scanner.getCurrentTokenEndPosition();
618 * Search the source position corresponding to the end of a given line
619 * number. Warning: returned position is 1-based index!
621 * @see Scanner#getLineEnd(int) We cannot directly use this method when
622 * linePtr field is not initialized.
624 * private int getLineEnd(int lineNumber) {
626 * if (this.scanner.linePtr != -1) { return
627 * this.scanner.getLineEnd(lineNumber); } if (this.lineEnds == null) return
628 * -1; if (lineNumber > this.lineEnds.length+1) return -1; if (lineNumber <=
629 * 0) return -1; if (lineNumber == this.lineEnds.length + 1) return
630 * this.scanner.eofPosition; return this.lineEnds[lineNumber-1]; // next
631 * line start one character behind the lineEnd of the previous line }
635 * Search the line number corresponding to a specific position. Warning:
636 * returned position is 1-based index!
638 * @see Scanner#getLineNumber(int) We cannot directly use this method when
639 * linePtr field is not initialized.
641 private int getLineNumber(int position) {
643 if (this.scanner.linePtr != -1) {
644 return this.scanner.getLineNumber(position);
646 if (this.lineEnds == null)
648 int length = this.lineEnds.length;
651 int g = 0, d = length - 1;
655 if (position < this.lineEnds[m]) {
657 } else if (position > this.lineEnds[m]) {
663 if (position < this.lineEnds[m]) {
672 * @see tag method reference
674 private Object parseArguments(Object receiver) throws InvalidInputException {
677 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type
680 char[] argName = null;
681 List arguments = new ArrayList(10);
682 int start = this.scanner.getCurrentTokenStartPosition();
684 // Parse arguments declaration if method reference
685 nextArg: while (this.index < this.scanner.eofPosition) {
687 // Read argument type reference
690 typeRef = parseQualifiedName(false);
691 } catch (InvalidInputException e) {
694 boolean firstArg = modulo == 0;
695 if (firstArg) { // verify position
698 } else if ((iToken % modulo) != 0) {
701 if (typeRef == null) {
703 && this.currentTokenType == ITerminalSymbols.TokenName.RPAREN) {
704 // verify characters after arguments declaration (expecting
705 // white space or end comment)
706 if (!verifySpaceOrEndComment()) {
707 int end = this.starPosition == -1 ? this.lineEnd
709 if (this.source[end] == '\n')
711 if (this.sourceParser != null)
712 this.sourceParser.problemReporter()
713 .javadocMalformedSeeReference(start, end);
716 this.lineStarted = true;
717 return createMethodReference(receiver, null);
723 // Read possible array declaration
725 long[] dimPositions = new long[20]; // assume that there won't be
726 // more than 20 dimensions...
727 if (readToken() == ITerminalSymbols.TokenName.LBRACKET) {
728 int dimStart = this.scanner.getCurrentTokenStartPosition();
729 while (readToken() == ITerminalSymbols.TokenName.LBRACKET) {
731 if (readToken() != ITerminalSymbols.TokenName.RBRACKET) {
735 dimPositions[dim++] = (((long) dimStart) << 32)
736 + this.scanner.getCurrentTokenEndPosition();
740 // Read argument name
741 long argNamePos = -1;
742 if (readToken() == ITerminalSymbols.TokenName.IDENTIFIER) {
744 if (firstArg) { // verify position
747 } else if ((iToken % modulo) != 1) {
750 if (argName == null) { // verify that all arguments name are
756 argName = this.scanner.getCurrentIdentifierSource();
757 argNamePos = (((long) this.scanner
758 .getCurrentTokenStartPosition()) << 32)
759 + this.scanner.getCurrentTokenEndPosition();
761 } else if (argName != null) { // verify that no argument name is
766 // Verify token position
770 if ((iToken % modulo) != (modulo - 1)) {
775 // Read separator or end arguments declaration
776 TokenName token = readToken();
777 char[] name = argName == null ? new char[0] : argName;
778 if (token == ITerminalSymbols.TokenName.COMMA) {
779 // Create new argument
780 Object argument = createArgumentReference(name, dim, typeRef,
781 dimPositions, argNamePos);
782 arguments.add(argument);
785 } else if (token == ITerminalSymbols.TokenName.RPAREN) {
786 // verify characters after arguments declaration (expecting
787 // white space or end comment)
788 if (!verifySpaceOrEndComment()) {
789 int end = this.starPosition == -1 ? this.lineEnd
791 if (this.source[end] == '\n')
793 if (this.sourceParser != null)
794 this.sourceParser.problemReporter()
795 .javadocMalformedSeeReference(start, end);
798 // Create new argument
799 Object argument = createArgumentReference(name, dim, typeRef,
800 dimPositions, argNamePos);
801 arguments.add(argument);
803 return createMethodReference(receiver, arguments);
809 // Something wrong happened => Invalid input
810 throw new InvalidInputException();
814 * Parse an URL link reference in
818 private boolean parseHref() throws InvalidInputException {
819 int start = this.scanner.getCurrentTokenStartPosition();
820 if (Character.toLowerCase(readChar()) == 'a') {
821 this.scanner.currentPosition = this.index;
822 if (readToken() == ITerminalSymbols.TokenName.IDENTIFIER) {
823 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // do not update line end
825 if (CharOperation.equals(this.scanner
826 .getCurrentIdentifierSource(), new char[] { 'h',
827 'r', 'e', 'f' }, false)
828 && readToken() == ITerminalSymbols.TokenName.EQUAL) {
829 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // do not update line end
830 if (readToken() == ITerminalSymbols.TokenName.STRINGDOUBLEQUOTE
831 || readToken() == ITerminalSymbols.TokenName.STRINGSINGLEQUOTE) {
832 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // do not update line
834 // Skip all characters after string literal until
835 // closing '>' (see bug 68726)
836 while (this.index <= this.lineEnd
837 && readToken() != ITerminalSymbols.TokenName.GREATER) {
838 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // do not update
841 if (this.currentTokenType == ITerminalSymbols.TokenName.GREATER) {
842 consumeToken(); // update line end as new lines
843 // are allowed in URL
845 while (readToken() != ITerminalSymbols.TokenName.LESS) {
846 if (this.scanner.currentPosition >= this.scanner.eofPosition
847 || this.scanner.currentCharacter == '@') {
848 // Reset position: we want to rescan
850 this.index = this.tokenPreviousPosition;
851 this.scanner.currentPosition = this.tokenPreviousPosition;
852 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
853 // Signal syntax error
854 if (this.sourceParser != null)
857 .javadocInvalidSeeUrlReference(
858 start, this.lineEnd);
863 this.currentTokenType = ITerminalSymbols.TokenName.NONE; // do not update
865 if (readChar() == '/') {
866 if (Character.toLowerCase(readChar()) == 'a') {
867 if (readChar() == '>') {
876 } catch (InvalidInputException ex) {
877 // Do nothing as we want to keep positions for error message
881 // Reset position: we want to rescan last token
882 this.index = this.tokenPreviousPosition;
883 this.scanner.currentPosition = this.tokenPreviousPosition;
884 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
885 // Signal syntax error
886 if (this.sourceParser != null)
887 this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(
888 start, this.lineEnd);
893 * Parse a method reference in
897 private Object parseMember(Object receiver) throws InvalidInputException {
899 this.identifierPtr = -1;
900 this.identifierLengthPtr = -1;
901 int start = this.scanner.getCurrentTokenStartPosition();
902 this.memberStart = start;
904 // Get member identifier
905 if (readToken() == ITerminalSymbols.TokenName.IDENTIFIER) {
907 pushIdentifier(true);
908 // Look for next token to know whether it's a field or method
910 int previousPosition = this.index;
911 if (readToken() == ITerminalSymbols.TokenName.LPAREN) {
913 start = this.scanner.getCurrentTokenStartPosition();
915 return parseArguments(receiver);
916 } catch (InvalidInputException e) {
917 int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ? this.scanner
918 .getCurrentTokenEndPosition()
919 : this.scanner.getCurrentTokenStartPosition();
920 end = end < this.lineEnd ? end : this.lineEnd;
921 if (this.sourceParser != null)
922 this.sourceParser.problemReporter()
923 .javadocInvalidSeeReferenceArgs(start, end);
928 // Reset position: we want to rescan last token
929 this.index = previousPosition;
930 this.scanner.currentPosition = previousPosition;
931 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
933 // Verify character(s) after identifier (expecting space or end
935 if (!verifySpaceOrEndComment()) {
936 int end = this.starPosition == -1 ? this.lineEnd
938 if (this.source[end] == '\n')
940 if (this.sourceParser != null)
941 this.sourceParser.problemReporter()
942 .javadocMalformedSeeReference(start, end);
945 return createFieldReference(receiver);
947 int end = getEndPosition() - 1;
948 end = start > end ? start : end;
949 if (this.sourceParser != null)
950 this.sourceParser.problemReporter().javadocInvalidSeeReference(
952 // Reset position: we want to rescan last token
953 this.index = this.tokenPreviousPosition;
954 this.scanner.currentPosition = this.tokenPreviousPosition;
955 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
960 * Parse @param tag declaration
962 protected boolean parseParam() {
964 // Store current token state
965 int start = this.tagSourceStart;
966 int end = this.tagSourceEnd;
969 // Push identifier next
970 TokenName token = readToken();
974 return pushParamName();
978 start = this.scanner.getCurrentTokenStartPosition();
979 end = getEndPosition();
981 start = this.tagSourceStart;
984 } catch (InvalidInputException e) {
985 end = getEndPosition();
988 // Reset position to avoid missing tokens when new line was encountered
989 this.index = this.tokenPreviousPosition;
990 this.scanner.currentPosition = this.tokenPreviousPosition;
991 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
994 if (this.sourceParser != null)
995 this.sourceParser.problemReporter().javadocMissingParamName(start,
1001 * Parse a qualified name and built a type reference if the syntax is valid.
1003 protected Object parseQualifiedName(boolean reset)
1004 throws InvalidInputException {
1006 // Reset identifier stack if requested
1008 this.identifierPtr = -1;
1009 this.identifierLengthPtr = -1;
1013 int primitiveToken = -1;
1014 nextToken: for (int iToken = 0;; iToken++) {
1015 TokenName token = readToken();
1018 if (((iToken % 2) > 0)) { // identifiers must be odd tokens
1021 pushIdentifier(iToken == 0);
1026 if ((iToken % 2) == 0) { // dots must be even tokens
1027 throw new InvalidInputException();
1032 // case ITerminalSymbols.TokenName.void :
1033 // case ITerminalSymbols.TokenName.boolean :
1034 // case ITerminalSymbols.TokenName.byte :
1035 // case ITerminalSymbols.TokenName.char :
1036 // case ITerminalSymbols.TokenName.double :
1037 // case ITerminalSymbols.TokenName.float :
1038 // case ITerminalSymbols.TokenName.int :
1039 // case ITerminalSymbols.TokenName.long :
1040 // case ITerminalSymbols.TokenName.short :
1041 // if (iToken > 0) {
1042 // throw new InvalidInputException();
1044 // pushIdentifier(true);
1045 // primitiveToken = token;
1053 if ((iToken % 2) == 0) { // cannot leave on a dot
1054 // Reset position: we want to rescan last token
1055 if (this.kind == DOM_PARSER && this.currentTokenType.compareTo (ITerminalSymbols.TokenName.NONE) > 0) {
1056 this.index = this.tokenPreviousPosition;
1057 this.scanner.currentPosition = this.tokenPreviousPosition;
1058 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1060 throw new InvalidInputException();
1065 // Reset position: we want to rescan last token
1066 if (this.currentTokenType.compareTo (ITerminalSymbols.TokenName.NONE) > 0) {
1067 this.index = this.tokenPreviousPosition;
1068 this.scanner.currentPosition = this.tokenPreviousPosition;
1069 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1071 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
1072 return createTypeReference(primitiveToken);
1076 * Parse a reference in
1080 protected boolean parseReference(boolean plain)
1081 throws InvalidInputException {
1082 Object typeRef = null;
1083 Object reference = null;
1084 int previousPosition = -1;
1085 int typeRefStartPosition = -1;
1086 nextToken: while (this.index < this.scanner.eofPosition) {
1087 previousPosition = this.index;
1088 TokenName token = readToken();
1090 case STRINGDOUBLEQUOTE: // @see "string"
1091 case STRINGSINGLEQUOTE:
1092 int start = this.scanner.getCurrentTokenStartPosition();
1094 // If typeRef != null we may raise a warning here to let user
1095 // know there's an unused reference...
1096 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug
1098 if (typeRef != null) {
1099 start = this.tagSourceEnd + 1;
1100 previousPosition = start;
1103 // verify end line (expecting empty or end comment)
1104 if (verifyEndLine(previousPosition)) {
1107 if (this.sourceParser != null)
1108 this.sourceParser.problemReporter()
1109 .javadocInvalidSeeReference(start, this.lineEnd);
1111 case LESS: // @see "<a href="URL#Value">label</a>
1113 start = this.scanner.getCurrentTokenStartPosition();
1116 // If typeRef != null we may raise a warning here to let
1117 // user know there's an unused reference...
1118 // Currently as javadoc 1.4.2 ignore it, we do the same (see
1120 if (typeRef != null) {
1121 start = this.tagSourceEnd + 1;
1122 previousPosition = start;
1125 // verify end line (expecting empty or end comment)
1126 if (verifyEndLine(previousPosition)) {
1129 if (this.sourceParser != null)
1132 .javadocInvalidSeeReference(start, this.lineEnd);
1136 if (this.scanner.currentCharacter == '#') { // @see ...#member
1138 reference = parseMember(typeRef);
1139 if (reference != null) {
1140 return pushSeeRef(reference, plain);
1146 if (typeRef == null) {
1147 typeRefStartPosition = this.scanner
1148 .getCurrentTokenStartPosition();
1149 typeRef = parseQualifiedName(true);
1158 // Verify that we got a reference
1159 if (reference == null)
1160 reference = typeRef;
1161 if (reference == null) {
1162 this.index = this.tokenPreviousPosition;
1163 this.scanner.currentPosition = this.tokenPreviousPosition;
1164 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1165 if (this.sourceParser != null)
1166 this.sourceParser.problemReporter().javadocMissingSeeReference(
1167 this.tagSourceStart, this.tagSourceEnd);
1171 // Reset position at the end of type reference
1172 this.index = this.lastIdentifierEndPosition + 1;
1173 this.scanner.currentPosition = this.index;
1174 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1176 // Verify that line end does not start with an open parenthese (which
1177 // could be a constructor reference wrongly written...)
1178 // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
1179 char ch = peekChar();
1181 if (this.sourceParser != null)
1182 this.sourceParser.problemReporter().javadocInvalidSeeReference(
1183 typeRefStartPosition, this.lineEnd);
1187 // Verify that we get white space after reference
1188 if (!verifySpaceOrEndComment()) {
1189 this.index = this.tokenPreviousPosition;
1190 this.scanner.currentPosition = this.tokenPreviousPosition;
1191 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1192 int end = this.starPosition == -1 ? this.lineEnd
1193 : this.starPosition;
1194 if (this.source[end] == '\n')
1196 if (this.sourceParser != null)
1199 .javadocMalformedSeeReference(typeRefStartPosition, end);
1203 // Everything is OK, store reference
1204 return pushSeeRef(reference, plain);
1208 * Parse @return tag declaration
1210 protected abstract boolean parseReturn();
1215 * @see tag declaration
1217 protected boolean parseSee(boolean plain) {
1218 int start = this.scanner.currentPosition;
1220 return parseReference(plain);
1221 } catch (InvalidInputException ex) {
1222 if (this.sourceParser != null)
1223 this.sourceParser.problemReporter().javadocInvalidSeeReference(
1224 start, getEndPosition());
1226 // Reset position to avoid missing tokens when new line was encountered
1227 this.index = this.tokenPreviousPosition;
1228 this.scanner.currentPosition = this.tokenPreviousPosition;
1229 this.currentTokenType = ITerminalSymbols.TokenName.NONE;
1234 * Parse @return tag declaration
1236 protected abstract boolean parseTag();
1239 * Parse @throws tag declaration
1241 protected boolean parseThrows(boolean real) {
1242 int start = this.scanner.currentPosition;
1244 Object typeRef = parseQualifiedName(true);
1245 if (typeRef == null) {
1246 if (this.sourceParser != null)
1247 this.sourceParser.problemReporter()
1248 .javadocMissingThrowsClassName(this.tagSourceStart,
1251 return pushThrowName(typeRef, real);
1253 } catch (InvalidInputException ex) {
1254 if (this.sourceParser != null)
1255 this.sourceParser.problemReporter().javadocInvalidThrowsClass(
1256 start, getEndPosition());
1262 * Return current character without move index position.
1264 private char peekChar() {
1265 int idx = this.index;
1266 char c = this.source[idx++];
1267 if (c == '\\' && this.source[idx] == 'u') {
1270 while (this.source[idx] == 'u')
1272 if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
1273 || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
1274 || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character
1275 .getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
1276 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1283 * push the consumeToken on the identifier stack. Increase the total number
1284 * of identifier in the stack.
1286 protected void pushIdentifier(boolean newLength) {
1288 int stackLength = this.identifierStack.length;
1289 if (++this.identifierPtr >= stackLength) {
1290 System.arraycopy(this.identifierStack, 0,
1291 this.identifierStack = new char[stackLength + 10][], 0,
1293 System.arraycopy(this.identifierPositionStack, 0,
1294 this.identifierPositionStack = new long[stackLength + 10],
1297 this.identifierStack[this.identifierPtr] = this.scanner
1298 .getCurrentIdentifierSource();
1299 this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32)
1300 + (this.scanner.currentPosition - 1);
1303 stackLength = this.identifierLengthStack.length;
1304 if (++this.identifierLengthPtr >= stackLength) {
1305 System.arraycopy(this.identifierLengthStack, 0,
1306 this.identifierLengthStack = new int[stackLength + 10],
1309 this.identifierLengthStack[this.identifierLengthPtr] = 1;
1311 this.identifierLengthStack[this.identifierLengthPtr]++;
1316 * Add a new obj on top of the ast stack. If new length is required, then
1317 * add also a new length in length stack.
1319 protected void pushOnAstStack(Object node, boolean newLength) {
1322 this.astLengthStack[++this.astLengthPtr] = 0;
1326 int stackLength = this.astStack.length;
1327 if (++this.astPtr >= stackLength) {
1329 .arraycopy(this.astStack, 0,
1330 this.astStack = new Object[stackLength
1331 + AstStackIncrement], 0, stackLength);
1332 this.astPtr = stackLength;
1334 this.astStack[this.astPtr] = node;
1337 stackLength = this.astLengthStack.length;
1338 if (++this.astLengthPtr >= stackLength) {
1339 System.arraycopy(this.astLengthStack, 0,
1340 this.astLengthStack = new int[stackLength
1341 + AstStackIncrement], 0, stackLength);
1343 this.astLengthStack[this.astLengthPtr] = 1;
1345 this.astLengthStack[this.astLengthPtr]++;
1350 * Push a param name in ast node stack.
1352 protected abstract boolean pushParamName();
1355 * Push a reference statement in ast node stack.
1357 protected abstract boolean pushSeeRef(Object statement, boolean plain);
1360 * Push a text element in ast node stack
1362 protected abstract void pushText(int start, int end);
1365 * Push a throws type ref in ast node stack.
1367 protected abstract boolean pushThrowName(Object typeRef, boolean real);
1370 * Read current character and move index position. Warning: scanner position
1371 * is unchanged using this method!
1373 protected char readChar() {
1375 char c = this.source[this.index++];
1376 if (c == '\\' && this.source[this.index] == 'u') {
1378 int pos = this.index;
1380 while (this.source[this.index] == 'u')
1382 if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
1384 .getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
1386 .getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character
1387 .getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
1388 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1390 // TODO (frederic) currently reset to previous position, perhaps
1391 // signal a syntax error would be more appropriate
1399 * Read token only if previous was consumed
1401 private TokenName readToken() throws InvalidInputException {
1402 if (this.currentTokenType.compareTo (ITerminalSymbols.TokenName.NONE) <= 0) {
1403 this.tokenPreviousPosition = this.scanner.currentPosition;
1404 this.currentTokenType = this.scanner.getNextToken();
1405 if (this.scanner.currentPosition > (this.lineEnd + 1)) { // be
1418 this.lineStarted = false;
1419 while (this.currentTokenType == ITerminalSymbols.TokenName.MULTIPLY) {
1420 this.currentTokenType = this.scanner.getNextToken();
1423 this.index = this.scanner.currentPosition;
1424 this.lineStarted = true; // after having read a token, line is
1425 // obviously started...
1427 return this.currentTokenType;
1430 private TokenName readTokenAndConsume() throws InvalidInputException {
1431 TokenName token = readToken();
1437 * Refresh start position and length of an inline tag.
1439 protected void refreshInlineTagPosition(int previousPosition) {
1440 // do nothing by default
1443 public String toString() {
1444 StringBuffer buffer = new StringBuffer();
1445 int startPos = this.scanner.currentPosition < this.index ? this.scanner.currentPosition
1447 int endPos = this.scanner.currentPosition < this.index ? this.index
1448 : this.scanner.currentPosition;
1449 if (startPos == this.source.length)
1450 return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
1451 if (endPos > this.source.length)
1452 return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
1454 char front[] = new char[startPos];
1455 System.arraycopy(this.source, 0, front, 0, startPos);
1457 int middleLength = (endPos - 1) - startPos + 1;
1459 if (middleLength > -1) {
1460 middle = new char[middleLength];
1461 System.arraycopy(this.source, startPos, middle, 0, middleLength);
1463 middle = CharOperation.NO_CHAR;
1466 char end[] = new char[this.source.length - (endPos - 1)];
1467 System.arraycopy(this.source, (endPos - 1) + 1, end, 0,
1468 this.source.length - (endPos - 1) - 1);
1470 buffer.append(front);
1471 if (this.scanner.currentPosition < this.index) {
1473 .append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
1476 .append("\n===============================\nParser index here -->"); //$NON-NLS-1$
1478 buffer.append(middle);
1479 if (this.scanner.currentPosition < this.index) {
1481 .append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
1484 .append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
1488 return buffer.toString();
1494 protected abstract void updateDocComment();
1499 protected void updateLineEnd() {
1500 while (this.index > (this.lineEnd + 1)) { // be sure to be on next
1501 // line (lineEnd is still on
1503 if (this.linePtr < this.lastLinePtr) {
1504 this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
1506 this.lineEnd = this.endComment;
1513 * Verify that end of the line only contains space characters or end of
1514 * comment. Note that end of comment may be preceeding by several contiguous
1517 private boolean verifyEndLine(int textPosition) {
1518 int startPosition = this.index;
1519 int previousPosition = this.index;
1520 this.starPosition = -1;
1521 char ch = readChar();
1522 nextChar: while (true) {
1526 if (this.kind == DOM_PARSER) {
1528 pushText(textPosition, previousPosition);
1530 this.index = previousPosition;
1532 case '\u000c': /* FORM FEED */
1533 case ' ': /* SPACE */
1534 case '\t': /* HORIZONTAL TABULATION */
1535 if (this.starPosition >= 0)
1539 this.starPosition = previousPosition;
1542 if (this.starPosition >= textPosition) {
1543 if (this.kind == DOM_PARSER) {
1545 pushText(textPosition, this.starPosition);
1554 previousPosition = this.index;
1557 this.index = startPosition;
1562 * Verify that some text exists after a @return tag. Text must be different
1563 * than end of comment which may be preceeding by several '*' chars.
1565 private boolean verifyCharsAfterReturnTag(int startPosition) {
1566 // Whitespace or inline tag closing brace
1567 int previousPosition = this.index;
1568 char ch = readChar();
1569 boolean malformed = true;
1570 while (Character.isWhitespace(ch)) {
1572 previousPosition = this.index;
1576 this.starPosition = -1;
1577 nextChar: while (this.index < this.source.length) {
1580 // valid whatever the number of star before last '/'
1581 this.starPosition = previousPosition;
1584 if (this.starPosition >= startPosition) { // valid only if a
1585 // star was previous
1590 // valid if any other character is encountered, even white
1592 this.index = startPosition;
1596 previousPosition = this.index;
1599 this.index = startPosition;
1604 * Verify characters after a name matches one of following conditions: 1-
1605 * first character is a white space 2- first character is a closing brace
1606 * *and* we're currently parsing an inline tag 3- are the end of comment
1607 * (several contiguous star ('*') characters may be found before the last
1608 * slash ('/') character).
1610 private boolean verifySpaceOrEndComment() {
1611 int startPosition = this.index;
1612 // Whitespace or inline tag closing brace
1613 char ch = peekChar();
1616 return this.inlineTagStarted;
1618 if (Character.isWhitespace(ch)) {
1623 int previousPosition = this.index;
1624 this.starPosition = -1;
1626 nextChar: while (this.index < this.source.length) {
1629 // valid whatever the number of star before last '/'
1630 this.starPosition = previousPosition;
1633 if (this.starPosition >= startPosition) { // valid only if a
1634 // star was previous
1639 // invalid whatever other character, even white spaces
1640 this.index = startPosition;
1644 previousPosition = this.index;
1647 this.index = startPosition;