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$
28 public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
30 public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
32 public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
34 public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
36 public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
38 public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
40 public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
42 public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
44 // tags expected positions
45 public final static int ORDERED_TAGS_NUMBER = 3;
47 public final static int PARAM_TAG_EXPECTED_ORDER = 0;
49 public final static int THROWS_TAG_EXPECTED_ORDER = 1;
51 public final static int SEE_TAG_EXPECTED_ORDER = 2;
53 // Kind of comment parser
54 public final static int COMPIL_PARSER = 0x00000001;
56 public final static int DOM_PARSER = 0x00000002;
59 public Scanner scanner;
61 public boolean checkDocComment = false;
64 protected boolean inherited, deprecated;
66 protected char[] source;
68 protected int index, endComment, lineEnd;
70 protected int tokenPreviousPosition, lastIdentifierEndPosition,
73 protected int textStart, memberStart;
75 protected int tagSourceStart, tagSourceEnd;
77 protected int inlineTagStart;
79 protected Parser sourceParser;
81 protected Object returnStatement;
83 protected boolean lineStarted = false, inlineTagStarted = false;
87 protected int[] lineEnds;
90 private int currentTokenType = -1;
93 private int linePtr, lastLinePtr;
96 protected int identifierPtr;
98 protected char[][] identifierStack;
100 protected int identifierLengthPtr;
102 protected int[] identifierLengthStack;
104 protected long[] identifierPositionStack;
107 protected static int AstStackIncrement = 10;
109 protected int astPtr;
111 protected Object[] astStack;
113 protected int astLengthPtr;
115 protected int[] astLengthStack;
117 protected AbstractCommentParser(Parser sourceParser) {
118 this.sourceParser = sourceParser;
119 this.scanner = new Scanner(false, false, false, false, false, null,
122 this.identifierStack = new char[20][];
123 this.identifierPositionStack = new long[20];
124 this.identifierLengthStack = new int[10];
125 this.astStack = new Object[30];
126 this.astLengthStack = new int[20];
130 * (non-Javadoc) Returns true if tag
132 * @deprecated is present in javadoc comment.
134 * If javadoc checking is enabled, will also construct an Javadoc node,
135 * which will be stored into Parser.javadoc slot for being consumed later
138 protected boolean parseComment(int javadocStart, int javadocEnd) {
140 boolean validComment = true;
142 // Init scanner position
143 this.scanner.resetTo(javadocStart, javadocEnd);
144 this.endComment = javadocEnd;
145 this.index = javadocStart;
146 readChar(); // starting '/'
147 int previousPosition = this.index;
148 readChar(); // first '*'
149 char nextCharacter = readChar(); // second '*'
151 // Init local variables
152 this.astLengthPtr = -1;
154 this.currentTokenType = -1;
155 this.inlineTagStarted = false;
156 this.inlineTagStart = -1;
157 this.lineStarted = false;
158 this.returnStatement = null;
159 this.inherited = false;
160 this.deprecated = false;
161 this.linePtr = getLineNumber(javadocStart);
162 this.lastLinePtr = getLineNumber(javadocEnd);
163 this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment
164 : this.scanner.getLineEnd(this.linePtr);
166 char previousChar = 0;
167 int invalidTagLineEnd = -1;
168 int invalidInlineTagLineEnd = -1;
170 // Loop on each comment character
171 while (this.index < this.endComment) {
172 previousPosition = this.index;
173 previousChar = nextCharacter;
175 // Calculate line end (cannot use this.scanner.linePtr as
176 // scanner does not parse line ends again)
177 if (this.index > (this.lineEnd + 1)) {
181 // Read next char only if token was consumed
182 if (this.currentTokenType < 0) {
183 nextCharacter = readChar(); // consider unicodes
185 previousPosition = this.scanner
186 .getCurrentTokenStartPosition();
187 switch (this.currentTokenType) {
188 case ITerminalSymbols.TokenNameRBRACE:
191 case ITerminalSymbols.TokenNameMULTIPLY:
195 nextCharacter = this.scanner.currentCharacter;
200 if (this.index >= this.endComment) {
204 switch (nextCharacter) {
206 boolean valid = false;
207 // Start tag parsing only if we have a java identifier start
208 // character and if we are on line beginning or at inline
210 if ((!this.lineStarted || previousChar == '{')) {
211 this.lineStarted = true;
212 if (this.inlineTagStarted) {
213 this.inlineTagStarted = false;
215 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
216 // Cannot have @ inside inline comment
217 if (this.sourceParser != null) {
218 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
219 : invalidInlineTagLineEnd;
220 this.sourceParser.problemReporter()
221 .javadocUnterminatedInlineTag(
222 this.inlineTagStart, end);
224 validComment = false;
225 if (this.lineStarted && this.textStart != -1
226 && this.textStart < previousPosition) {
227 pushText(this.textStart, previousPosition);
229 if (this.kind == DOM_PARSER)
230 refreshInlineTagPosition(previousPosition);
232 if (previousChar == '{') {
233 if (this.textStart != -1
234 && this.textStart < this.inlineTagStart) {
235 pushText(this.textStart, this.inlineTagStart);
237 this.inlineTagStarted = true;
238 invalidInlineTagLineEnd = this.lineEnd;
239 } else if (this.textStart != -1
240 && this.textStart < invalidTagLineEnd) {
241 pushText(this.textStart, invalidTagLineEnd);
243 this.scanner.resetTo(this.index, this.endComment);
244 this.currentTokenType = -1; // flush token cache at line
247 int token = readTokenAndConsume();
248 this.tagSourceStart = this.scanner
249 .getCurrentTokenStartPosition();
250 this.tagSourceEnd = this.scanner
251 .getCurrentTokenEndPosition();
252 char[] tag = this.scanner
253 .getCurrentIdentifierSource(); // first
259 if (this.kind == DOM_PARSER) {
260 // For DOM parser, try to get tag name other
261 // than java identifier
263 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
265 int le = this.lineEnd;
266 char pc = peekChar();
267 tagNameToken: while (tk != ITerminalSymbols.TokenNameEOF) {
268 this.tagSourceEnd = this.scanner
269 .getCurrentTokenEndPosition();
271 // !, ", #, %, &, ', -, :, <, >, * chars and
272 // spaces are not allowed in tag names
281 // case '-': allowed in tag names as
282 // this character is often used in
283 // doclets (bug 68087)
286 case '*': // break for '*' as this is
287 // perhaps the end of comment
292 || Character.isWhitespace(pc))
295 tk = readTokenAndConsume();
298 int length = this.tagSourceEnd
299 - this.tagSourceStart + 1;
300 tag = new char[length];
301 System.arraycopy(this.source,
302 this.tagSourceStart, tag, 0, length);
303 this.index = this.tagSourceEnd + 1;
304 this.scanner.currentPosition = this.tagSourceEnd + 1;
305 this.tagSourceStart = previousPosition;
309 case ITerminalSymbols.TokenNameIdentifier:
310 if (CharOperation.equals(tag, TAG_DEPRECATED)) {
311 this.deprecated = true;
312 if (this.kind == DOM_PARSER) {
317 } else if (CharOperation.equals(tag,
319 // inhibits inherited flag when tags have
320 // been already stored
322 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
323 // Note that for DOM_PARSER, nodes stack may
324 // be not empty even no '@' tag
325 // was encountered in comment. But it cannot
326 // be the case for COMPILER_PARSER
327 // and so is enough as it is only this
328 // parser which signals the missing tag
330 this.inherited = this.astPtr == -1;
331 if (this.kind == DOM_PARSER) {
336 } else if (CharOperation.equals(tag, TAG_PARAM)) {
337 valid = parseParam();
338 } else if (CharOperation.equals(tag,
340 valid = parseThrows(false);
341 } else if (CharOperation.equals(tag, TAG_SEE)) {
342 if (this.inlineTagStarted) {
344 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
345 // Cannot have @see inside inline
348 if (this.sourceParser != null)
351 .javadocUnexpectedTag(
355 valid = parseSee(false);
357 } else if (CharOperation.equals(tag, TAG_LINK)) {
358 if (this.inlineTagStarted) {
359 valid = parseSee(false);
362 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
363 // Cannot have @link outside inline
366 if (this.sourceParser != null)
369 .javadocUnexpectedTag(
373 } else if (CharOperation.equals(tag,
375 if (this.inlineTagStarted) {
376 valid = parseSee(true);
384 case ITerminalSymbols.TokenNamereturn:
385 valid = parseReturn();
386 // verify characters after return tag (we're
387 // expecting text description)
388 if (!verifyCharsAfterReturnTag(this.index)) {
389 if (this.sourceParser != null) {
390 int end = this.starPosition == -1
391 || this.lineEnd < this.starPosition ? this.lineEnd
393 this.sourceParser.problemReporter()
400 // case ITerminalSymbols.TokenNamethrows :
401 // valid = parseThrows(true);
404 if (this.kind == DOM_PARSER) {
406 case ITerminalSymbols.TokenNameabstract:
408 // ITerminalSymbols.TokenNameassert:
410 // ITerminalSymbols.TokenNameboolean:
411 case ITerminalSymbols.TokenNamebreak:
412 // case ITerminalSymbols.TokenNamebyte:
413 case ITerminalSymbols.TokenNamecase:
414 case ITerminalSymbols.TokenNamecatch:
415 // case ITerminalSymbols.TokenNamechar:
416 case ITerminalSymbols.TokenNameclass:
417 case ITerminalSymbols.TokenNamecontinue:
418 case ITerminalSymbols.TokenNamedefault:
419 case ITerminalSymbols.TokenNamedo:
421 // ITerminalSymbols.TokenNamedouble:
422 case ITerminalSymbols.TokenNameelse:
423 case ITerminalSymbols.TokenNameextends:
424 // case ITerminalSymbols.TokenNamefalse:
425 case ITerminalSymbols.TokenNamefinal:
426 case ITerminalSymbols.TokenNamefinally:
427 // case ITerminalSymbols.TokenNamefloat:
428 case ITerminalSymbols.TokenNamefor:
429 case ITerminalSymbols.TokenNameif:
430 case ITerminalSymbols.TokenNameimplements:
432 // ITerminalSymbols.TokenNameimport:
433 case ITerminalSymbols.TokenNameinstanceof:
434 // case ITerminalSymbols.TokenNameint:
435 case ITerminalSymbols.TokenNameinterface:
436 // case ITerminalSymbols.TokenNamelong:
438 // ITerminalSymbols.TokenNamenative:
439 case ITerminalSymbols.TokenNamenew:
440 // case ITerminalSymbols.TokenNamenull:
442 // ITerminalSymbols.TokenNamepackage:
443 case ITerminalSymbols.TokenNameprivate:
444 case ITerminalSymbols.TokenNameprotected:
445 case ITerminalSymbols.TokenNamepublic:
446 // case ITerminalSymbols.TokenNameshort:
447 case ITerminalSymbols.TokenNamestatic:
449 // ITerminalSymbols.TokenNamestrictfp:
450 case ITerminalSymbols.TokenNamesuper:
451 case ITerminalSymbols.TokenNameswitch:
453 // ITerminalSymbols.TokenNamesynchronized:
454 // case ITerminalSymbols.TokenNamethis:
455 case ITerminalSymbols.TokenNamethrow:
457 // ITerminalSymbols.TokenNametransient:
458 // case ITerminalSymbols.TokenNametrue:
459 case ITerminalSymbols.TokenNametry:
460 // case ITerminalSymbols.TokenNamevoid:
462 // ITerminalSymbols.TokenNamevolatile:
463 case ITerminalSymbols.TokenNamewhile:
469 this.textStart = this.index;
472 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
473 // do not stop the inline tag when error is
474 // encountered to get text after
475 validComment = false;
477 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
478 // for DOM AST node, store tag as text in case
480 if (this.kind == DOM_PARSER) {
482 this.textStart = this.tagSourceEnd + 1;
483 invalidTagLineEnd = this.lineEnd;
486 } catch (InvalidInputException e) {
493 if (this.lineStarted && this.textStart < previousPosition) {
494 pushText(this.textStart, previousPosition);
496 this.lineStarted = false;
501 if (this.inlineTagStarted) {
502 if (this.lineStarted && this.textStart != -1
503 && this.textStart < previousPosition) {
504 pushText(this.textStart, previousPosition);
506 if (this.kind == DOM_PARSER)
507 refreshInlineTagPosition(previousPosition);
508 this.textStart = this.index;
509 this.inlineTagStarted = false;
511 if (!this.lineStarted) {
512 this.textStart = previousPosition;
515 this.lineStarted = true;
518 if (this.inlineTagStarted) {
519 this.inlineTagStarted = false;
521 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
522 // Cannot have opening brace in inline comment
523 if (this.sourceParser != null) {
524 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
525 : invalidInlineTagLineEnd;
526 this.sourceParser.problemReporter()
527 .javadocUnterminatedInlineTag(
528 this.inlineTagStart, end);
530 if (this.lineStarted && this.textStart != -1
531 && this.textStart < previousPosition) {
532 pushText(this.textStart, previousPosition);
534 if (this.kind == DOM_PARSER)
535 refreshInlineTagPosition(previousPosition);
537 if (!this.lineStarted) {
538 this.textStart = previousPosition;
540 this.lineStarted = true;
541 this.inlineTagStart = previousPosition;
544 case '\u000c': /* FORM FEED */
545 case ' ': /* SPACE */
546 case '\t': /* HORIZONTAL TABULATION */
547 // do nothing for space or '*' characters
550 if (!this.lineStarted) {
551 this.textStart = previousPosition;
553 this.lineStarted = true;
557 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
558 // Cannot leave comment inside inline comment
559 if (this.inlineTagStarted) {
560 this.inlineTagStarted = false;
561 if (this.sourceParser != null) {
562 int end = previousPosition < invalidInlineTagLineEnd ? previousPosition
563 : invalidInlineTagLineEnd;
564 if (this.index >= this.endComment)
565 end = invalidInlineTagLineEnd;
566 this.sourceParser.problemReporter()
567 .javadocUnterminatedInlineTag(this.inlineTagStart,
570 if (this.lineStarted && this.textStart != -1
571 && this.textStart < previousPosition) {
572 pushText(this.textStart, previousPosition);
574 if (this.kind == DOM_PARSER) {
575 refreshInlineTagPosition(previousPosition);
577 } else if (this.lineStarted && this.textStart < previousPosition) {
578 pushText(this.textStart, previousPosition);
581 } catch (Exception ex) {
582 validComment = false;
587 private void consumeToken() {
588 this.currentTokenType = -1; // flush token cache
592 protected abstract Object createArgumentReference(char[] name, int dim,
593 Object typeRef, long[] dimPos, long argNamePos)
594 throws InvalidInputException;
596 protected abstract Object createFieldReference(Object receiver)
597 throws InvalidInputException;
599 protected abstract Object createMethodReference(Object receiver,
600 List arguments) throws InvalidInputException;
602 protected Object createReturnStatement() {
606 protected abstract Object createTypeReference(int primitiveToken);
608 private int getEndPosition() {
609 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
612 return this.scanner.getCurrentTokenEndPosition();
617 * Search the source position corresponding to the end of a given line
618 * number. Warning: returned position is 1-based index!
620 * @see Scanner#getLineEnd(int) We cannot directly use this method when
621 * linePtr field is not initialized.
623 * private int getLineEnd(int lineNumber) {
625 * if (this.scanner.linePtr != -1) { return
626 * this.scanner.getLineEnd(lineNumber); } if (this.lineEnds == null) return
627 * -1; if (lineNumber > this.lineEnds.length+1) return -1; if (lineNumber <=
628 * 0) return -1; if (lineNumber == this.lineEnds.length + 1) return
629 * this.scanner.eofPosition; return this.lineEnds[lineNumber-1]; // next
630 * line start one character behind the lineEnd of the previous line }
634 * Search the line number corresponding to a specific position. Warning:
635 * returned position is 1-based index!
637 * @see Scanner#getLineNumber(int) We cannot directly use this method when
638 * linePtr field is not initialized.
640 private int getLineNumber(int position) {
642 if (this.scanner.linePtr != -1) {
643 return this.scanner.getLineNumber(position);
645 if (this.lineEnds == null)
647 int length = this.lineEnds.length;
650 int g = 0, d = length - 1;
654 if (position < this.lineEnds[m]) {
656 } else if (position > this.lineEnds[m]) {
662 if (position < this.lineEnds[m]) {
671 * @see tag method reference
673 private Object parseArguments(Object receiver) throws InvalidInputException {
676 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type
679 char[] argName = null;
680 List arguments = new ArrayList(10);
681 int start = this.scanner.getCurrentTokenStartPosition();
683 // Parse arguments declaration if method reference
684 nextArg: while (this.index < this.scanner.eofPosition) {
686 // Read argument type reference
689 typeRef = parseQualifiedName(false);
690 } catch (InvalidInputException e) {
693 boolean firstArg = modulo == 0;
694 if (firstArg) { // verify position
697 } else if ((iToken % modulo) != 0) {
700 if (typeRef == null) {
702 && this.currentTokenType == ITerminalSymbols.TokenNameRPAREN) {
703 // verify characters after arguments declaration (expecting
704 // white space or end comment)
705 if (!verifySpaceOrEndComment()) {
706 int end = this.starPosition == -1 ? this.lineEnd
708 if (this.source[end] == '\n')
710 if (this.sourceParser != null)
711 this.sourceParser.problemReporter()
712 .javadocMalformedSeeReference(start, end);
715 this.lineStarted = true;
716 return createMethodReference(receiver, null);
722 // Read possible array declaration
724 long[] dimPositions = new long[20]; // assume that there won't be
725 // more than 20 dimensions...
726 if (readToken() == ITerminalSymbols.TokenNameLBRACKET) {
727 int dimStart = this.scanner.getCurrentTokenStartPosition();
728 while (readToken() == ITerminalSymbols.TokenNameLBRACKET) {
730 if (readToken() != ITerminalSymbols.TokenNameRBRACKET) {
734 dimPositions[dim++] = (((long) dimStart) << 32)
735 + this.scanner.getCurrentTokenEndPosition();
739 // Read argument name
740 long argNamePos = -1;
741 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
743 if (firstArg) { // verify position
746 } else if ((iToken % modulo) != 1) {
749 if (argName == null) { // verify that all arguments name are
755 argName = this.scanner.getCurrentIdentifierSource();
756 argNamePos = (((long) this.scanner
757 .getCurrentTokenStartPosition()) << 32)
758 + this.scanner.getCurrentTokenEndPosition();
760 } else if (argName != null) { // verify that no argument name is
765 // Verify token position
769 if ((iToken % modulo) != (modulo - 1)) {
774 // Read separator or end arguments declaration
775 int token = readToken();
776 char[] name = argName == null ? new char[0] : argName;
777 if (token == ITerminalSymbols.TokenNameCOMMA) {
778 // Create new argument
779 Object argument = createArgumentReference(name, dim, typeRef,
780 dimPositions, argNamePos);
781 arguments.add(argument);
784 } else if (token == ITerminalSymbols.TokenNameRPAREN) {
785 // verify characters after arguments declaration (expecting
786 // white space or end comment)
787 if (!verifySpaceOrEndComment()) {
788 int end = this.starPosition == -1 ? this.lineEnd
790 if (this.source[end] == '\n')
792 if (this.sourceParser != null)
793 this.sourceParser.problemReporter()
794 .javadocMalformedSeeReference(start, end);
797 // Create new argument
798 Object argument = createArgumentReference(name, dim, typeRef,
799 dimPositions, argNamePos);
800 arguments.add(argument);
802 return createMethodReference(receiver, arguments);
808 // Something wrong happened => Invalid input
809 throw new InvalidInputException();
813 * Parse an URL link reference in
817 private boolean parseHref() throws InvalidInputException {
818 int start = this.scanner.getCurrentTokenStartPosition();
819 if (Character.toLowerCase(readChar()) == 'a') {
820 this.scanner.currentPosition = this.index;
821 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
822 this.currentTokenType = -1; // do not update line end
824 if (CharOperation.equals(this.scanner
825 .getCurrentIdentifierSource(), new char[] { 'h',
826 'r', 'e', 'f' }, false)
827 && readToken() == ITerminalSymbols.TokenNameEQUAL) {
828 this.currentTokenType = -1; // do not update line end
829 if (readToken() == ITerminalSymbols.TokenNameStringDoubleQuote
830 || readToken() == ITerminalSymbols.TokenNameStringSingleQuote) {
831 this.currentTokenType = -1; // do not update line
833 // Skip all characters after string literal until
834 // closing '>' (see bug 68726)
835 while (this.index <= this.lineEnd
836 && readToken() != ITerminalSymbols.TokenNameGREATER) {
837 this.currentTokenType = -1; // do not update
840 if (this.currentTokenType == ITerminalSymbols.TokenNameGREATER) {
841 consumeToken(); // update line end as new lines
842 // are allowed in URL
844 while (readToken() != ITerminalSymbols.TokenNameLESS) {
845 if (this.scanner.currentPosition >= this.scanner.eofPosition
846 || this.scanner.currentCharacter == '@') {
847 // Reset position: we want to rescan
849 this.index = this.tokenPreviousPosition;
850 this.scanner.currentPosition = this.tokenPreviousPosition;
851 this.currentTokenType = -1;
852 // Signal syntax error
853 if (this.sourceParser != null)
856 .javadocInvalidSeeUrlReference(
857 start, this.lineEnd);
862 this.currentTokenType = -1; // do not update
864 if (readChar() == '/') {
865 if (Character.toLowerCase(readChar()) == 'a') {
866 if (readChar() == '>') {
875 } catch (InvalidInputException ex) {
876 // Do nothing as we want to keep positions for error message
880 // Reset position: we want to rescan last token
881 this.index = this.tokenPreviousPosition;
882 this.scanner.currentPosition = this.tokenPreviousPosition;
883 this.currentTokenType = -1;
884 // Signal syntax error
885 if (this.sourceParser != null)
886 this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(
887 start, this.lineEnd);
892 * Parse a method reference in
896 private Object parseMember(Object receiver) throws InvalidInputException {
898 this.identifierPtr = -1;
899 this.identifierLengthPtr = -1;
900 int start = this.scanner.getCurrentTokenStartPosition();
901 this.memberStart = start;
903 // Get member identifier
904 if (readToken() == ITerminalSymbols.TokenNameIdentifier) {
906 pushIdentifier(true);
907 // Look for next token to know whether it's a field or method
909 int previousPosition = this.index;
910 if (readToken() == ITerminalSymbols.TokenNameLPAREN) {
912 start = this.scanner.getCurrentTokenStartPosition();
914 return parseArguments(receiver);
915 } catch (InvalidInputException e) {
916 int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ? this.scanner
917 .getCurrentTokenEndPosition()
918 : this.scanner.getCurrentTokenStartPosition();
919 end = end < this.lineEnd ? end : this.lineEnd;
920 if (this.sourceParser != null)
921 this.sourceParser.problemReporter()
922 .javadocInvalidSeeReferenceArgs(start, end);
927 // Reset position: we want to rescan last token
928 this.index = previousPosition;
929 this.scanner.currentPosition = previousPosition;
930 this.currentTokenType = -1;
932 // Verify character(s) after identifier (expecting space or end
934 if (!verifySpaceOrEndComment()) {
935 int end = this.starPosition == -1 ? this.lineEnd
937 if (this.source[end] == '\n')
939 if (this.sourceParser != null)
940 this.sourceParser.problemReporter()
941 .javadocMalformedSeeReference(start, end);
944 return createFieldReference(receiver);
946 int end = getEndPosition() - 1;
947 end = start > end ? start : end;
948 if (this.sourceParser != null)
949 this.sourceParser.problemReporter().javadocInvalidSeeReference(
951 // Reset position: we want to rescan last token
952 this.index = this.tokenPreviousPosition;
953 this.scanner.currentPosition = this.tokenPreviousPosition;
954 this.currentTokenType = -1;
959 * Parse @param tag declaration
961 protected boolean parseParam() {
963 // Store current token state
964 int start = this.tagSourceStart;
965 int end = this.tagSourceEnd;
968 // Push identifier next
969 int token = readToken();
971 case ITerminalSymbols.TokenNameIdentifier:
973 return pushParamName();
974 case ITerminalSymbols.TokenNameEOF:
977 start = this.scanner.getCurrentTokenStartPosition();
978 end = getEndPosition();
980 start = this.tagSourceStart;
983 } catch (InvalidInputException e) {
984 end = getEndPosition();
987 // Reset position to avoid missing tokens when new line was encountered
988 this.index = this.tokenPreviousPosition;
989 this.scanner.currentPosition = this.tokenPreviousPosition;
990 this.currentTokenType = -1;
993 if (this.sourceParser != null)
994 this.sourceParser.problemReporter().javadocMissingParamName(start,
1000 * Parse a qualified name and built a type reference if the syntax is valid.
1002 protected Object parseQualifiedName(boolean reset)
1003 throws InvalidInputException {
1005 // Reset identifier stack if requested
1007 this.identifierPtr = -1;
1008 this.identifierLengthPtr = -1;
1012 int primitiveToken = -1;
1013 nextToken: for (int iToken = 0;; iToken++) {
1014 int token = readToken();
1016 case ITerminalSymbols.TokenNameIdentifier:
1017 if (((iToken % 2) > 0)) { // identifiers must be odd tokens
1020 pushIdentifier(iToken == 0);
1024 case ITerminalSymbols.TokenNameDOT:
1025 if ((iToken % 2) == 0) { // dots must be even tokens
1026 throw new InvalidInputException();
1031 // case ITerminalSymbols.TokenNamevoid :
1032 // case ITerminalSymbols.TokenNameboolean :
1033 // case ITerminalSymbols.TokenNamebyte :
1034 // case ITerminalSymbols.TokenNamechar :
1035 // case ITerminalSymbols.TokenNamedouble :
1036 // case ITerminalSymbols.TokenNamefloat :
1037 // case ITerminalSymbols.TokenNameint :
1038 // case ITerminalSymbols.TokenNamelong :
1039 // case ITerminalSymbols.TokenNameshort :
1040 // if (iToken > 0) {
1041 // throw new InvalidInputException();
1043 // pushIdentifier(true);
1044 // primitiveToken = token;
1052 if ((iToken % 2) == 0) { // cannot leave on a dot
1053 // Reset position: we want to rescan last token
1054 if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
1055 this.index = this.tokenPreviousPosition;
1056 this.scanner.currentPosition = this.tokenPreviousPosition;
1057 this.currentTokenType = -1;
1059 throw new InvalidInputException();
1064 // Reset position: we want to rescan last token
1065 if (this.currentTokenType != -1) {
1066 this.index = this.tokenPreviousPosition;
1067 this.scanner.currentPosition = this.tokenPreviousPosition;
1068 this.currentTokenType = -1;
1070 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
1071 return createTypeReference(primitiveToken);
1075 * Parse a reference in
1079 protected boolean parseReference(boolean plain)
1080 throws InvalidInputException {
1081 Object typeRef = null;
1082 Object reference = null;
1083 int previousPosition = -1;
1084 int typeRefStartPosition = -1;
1085 nextToken: while (this.index < this.scanner.eofPosition) {
1086 previousPosition = this.index;
1087 int token = readToken();
1089 case ITerminalSymbols.TokenNameStringDoubleQuote: // @see "string"
1090 case ITerminalSymbols.TokenNameStringSingleQuote:
1091 int start = this.scanner.getCurrentTokenStartPosition();
1093 // If typeRef != null we may raise a warning here to let user
1094 // know there's an unused reference...
1095 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug
1097 if (typeRef != null) {
1098 start = this.tagSourceEnd + 1;
1099 previousPosition = start;
1102 // verify end line (expecting empty or end comment)
1103 if (verifyEndLine(previousPosition)) {
1106 if (this.sourceParser != null)
1107 this.sourceParser.problemReporter()
1108 .javadocInvalidSeeReference(start, this.lineEnd);
1110 case ITerminalSymbols.TokenNameLESS: // @see "<a
1111 // 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);
1135 case ITerminalSymbols.TokenNameERROR:
1136 if (this.scanner.currentCharacter == '#') { // @see ...#member
1138 reference = parseMember(typeRef);
1139 if (reference != null) {
1140 return pushSeeRef(reference, plain);
1145 case ITerminalSymbols.TokenNameIdentifier:
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 = -1;
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 = -1;
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 = -1;
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 = -1;
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 int readToken() throws InvalidInputException {
1402 if (this.currentTokenType < 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.TokenNameMULTIPLY) {
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 int readTokenAndConsume() throws InvalidInputException {
1431 int 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;