removed Makefile; lifted repo/org.ibex.tool/src/ to src/
[org.ibex.tool.git] / src / org / eclipse / jdt / internal / compiler / parser / AbstractCommentParser.java
diff --git a/src/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java b/src/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
new file mode 100644 (file)
index 0000000..ca99505
--- /dev/null
@@ -0,0 +1,1402 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+
+/**
+ * Parser specialized for decoding javadoc comments
+ */
+public abstract class AbstractCommentParser {
+
+       // recognized tags
+       public static final char[] TAG_DEPRECATED = "deprecated".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
+       public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
+       
+       // tags expected positions
+       public final static int ORDERED_TAGS_NUMBER = 3;
+       public final static int PARAM_TAG_EXPECTED_ORDER = 0;
+       public final static int THROWS_TAG_EXPECTED_ORDER = 1;
+       public final static int SEE_TAG_EXPECTED_ORDER = 2;
+       
+       // Kind of comment parser
+       public final static int COMPIL_PARSER = 0x00000001;
+       public final static int DOM_PARSER = 0x00000002;
+       
+       // Public fields
+       public Scanner scanner;
+       public boolean checkDocComment = false;
+       
+       // Protected fields
+       protected boolean inherited, deprecated;
+       protected char[] source;
+       protected int index, endComment, lineEnd;
+       protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
+       protected int textStart, memberStart;
+       protected int tagSourceStart, tagSourceEnd;
+       protected int inlineTagStart;
+       protected Parser sourceParser;
+       protected Object returnStatement;
+       protected boolean lineStarted = false, inlineTagStarted = false;
+       protected int kind;
+       protected int[] lineEnds;
+       
+       // Private fields
+       private int currentTokenType = -1;
+       
+       // Line pointers
+       private int linePtr, lastLinePtr;
+       
+       // Identifier stack
+       protected int identifierPtr;
+       protected char[][] identifierStack;
+       protected int identifierLengthPtr;
+       protected int[] identifierLengthStack;
+       protected long[] identifierPositionStack;
+       // Ast stack
+       protected static int AstStackIncrement = 10;
+       protected int astPtr;
+       protected Object[] astStack;
+       protected int astLengthPtr;
+       protected int[] astLengthStack;
+
+       protected AbstractCommentParser(Parser sourceParser) {
+               this.sourceParser = sourceParser;
+               this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/);
+               this.identifierStack = new char[20][];
+               this.identifierPositionStack = new long[20];
+               this.identifierLengthStack = new int[10];
+               this.astStack = new Object[30];
+               this.astLengthStack = new int[20];
+       }
+
+       /* (non-Javadoc)
+        * Returns true if tag @deprecated is present in javadoc comment.
+        * 
+        * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
+        * slot for being consumed later on.
+        */
+       protected boolean parseComment(int javadocStart, int javadocEnd) {
+
+               boolean validComment = true;
+               try {
+                       // Init scanner position
+                       this.scanner.resetTo(javadocStart, javadocEnd);
+                       this.endComment = javadocEnd;
+                       this.index = javadocStart;
+                       readChar(); // starting '/'
+                       int previousPosition = this.index;
+                       readChar(); // first '*'
+                       char nextCharacter= readChar(); // second '*'
+                       
+                       // Init local variables
+                       this.astLengthPtr = -1;
+                       this.astPtr = -1;
+                       this.currentTokenType = -1;
+                       this.inlineTagStarted = false;
+                       this.inlineTagStart = -1;
+                       this.lineStarted = false;
+                       this.returnStatement = null;
+                       this.inherited = false;
+                       this.deprecated = false;
+                       this.linePtr = getLineNumber(javadocStart);
+                       this.lastLinePtr = getLineNumber(javadocEnd);
+                       this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
+                       this.textStart = -1;
+                       char previousChar = 0;
+                       int invalidTagLineEnd = -1;
+                       int invalidInlineTagLineEnd = -1;
+                       
+                       // Loop on each comment character
+                       while (this.index < this.endComment) {
+                               previousPosition = this.index;
+                               previousChar = nextCharacter;
+                               
+                               // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
+                               if (this.index > (this.lineEnd+1)) {
+                                       updateLineEnd();
+                               }
+                               
+                               // Read next char only if token was consumed
+                               if (this.currentTokenType < 0) {
+                                       nextCharacter = readChar(); // consider unicodes
+                               } else {
+                                       previousPosition = this.scanner.getCurrentTokenStartPosition();
+                                       switch (this.currentTokenType) {
+                                               case TerminalTokens.TokenNameRBRACE:
+                                                       nextCharacter = '}';
+                                                       break;
+                                               case TerminalTokens.TokenNameMULTIPLY:
+                                                       nextCharacter = '*';
+                                                       break;
+                                       default:
+                                                       nextCharacter = this.scanner.currentCharacter;
+                                       }
+                                       consumeToken();
+                               }
+                       
+                               if (this.index >= this.endComment) {
+                                       break;
+                               }
+                               
+                               switch (nextCharacter) {
+                                       case '@' :
+                                               boolean valid = false;
+                                               // Start tag parsing only if we have a java identifier start character and if we are on line beginning or at inline tag beginning
+                                               if ((!this.lineStarted || previousChar == '{')) {
+                                                       this.lineStarted = true;
+                                                       if (this.inlineTagStarted) {
+                                                               this.inlineTagStarted = false;
+                                                               // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
+                                                               // Cannot have @ inside inline comment
+                                                               if (this.sourceParser != null) {
+                                                                       int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
+                                                                       this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
+                                                               }
+                                                               validComment = false;
+                                                               if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
+                                                                       pushText(this.textStart, previousPosition);
+                                                               }
+                                                               if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
+                                                       }
+                                                       if (previousChar == '{') {
+                                                               if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
+                                                                       pushText(this.textStart, this.inlineTagStart);
+                                                               }
+                                                               this.inlineTagStarted = true;
+                                                               invalidInlineTagLineEnd = this.lineEnd;
+                                                       } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
+                                                               pushText(this.textStart, invalidTagLineEnd);
+                                                       }
+                                                       this.scanner.resetTo(this.index, this.endComment);
+                                                       this.currentTokenType = -1; // flush token cache at line begin
+                                                       try {
+                                                               int token = readTokenAndConsume();
+                                                               this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
+                                                               this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
+                                                               char[] tag = this.scanner.getCurrentIdentifierSource(); // first token is either an identifier or a keyword
+                                                               if (this.kind == DOM_PARSER) {
+                                                                       // For DOM parser, try to get tag name other than java identifier
+                                                                       // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
+                                                                       int tk = token;
+                                                                       int le = this.lineEnd;
+                                                                       char pc = peekChar();
+                                                                       tagNameToken: while (tk != TerminalTokens.TokenNameEOF) {
+                                                                               this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
+                                                                               token = tk;
+                                                                               // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names
+                                                                               switch (pc) {
+                                                                                       case '}':
+                                                                                       case '!':
+                                                                                       case '#':
+                                                                                       case '%':
+                                                                                       case '&':
+                                                                                       case '\'':
+                                                                                       case ':':
+                                                                                       // case '-': allowed in tag names as this character is often used in doclets (bug 68087)
+                                                                                       case '<':
+                                                                                       case '>':
+                                                                                       case '*': // break for '*' as this is perhaps the end of comment (bug 65288)
+                                                                                               break tagNameToken;
+                                                                                       default:
+                                                                                               if (pc == ' ' || Character.isWhitespace(pc)) break tagNameToken;
+                                                                               }
+                                                                               tk = readTokenAndConsume();
+                                                                               pc = peekChar();
+                                                                       }
+                                                                       int length = this.tagSourceEnd-this.tagSourceStart+1;
+                                                                       tag = new char[length];
+                                                                       System.arraycopy(this.source, this.tagSourceStart, tag, 0, length);
+                                                                       this.index = this.tagSourceEnd+1;
+                                                                       this.scanner.currentPosition = this.tagSourceEnd+1;
+                                                                       this.tagSourceStart = previousPosition;
+                                                                       this.lineEnd = le;
+                                                               }
+                                                               switch (token) {
+                                                                       case TerminalTokens.TokenNameIdentifier :
+                                                                               if (CharOperation.equals(tag, TAG_DEPRECATED)) {
+                                                                                       this.deprecated = true;
+                                                                                       if (this.kind == DOM_PARSER) {
+                                                                                               valid = parseTag();
+                                                                                       } else {
+                                                                                               valid = true;
+                                                                                       }
+                                                                               } else if (CharOperation.equals(tag, TAG_INHERITDOC)) {
+                                                                                       // inhibits inherited flag when tags have been already stored
+                                                                                       // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
+                                                                                       // Note that for DOM_PARSER, nodes stack may be not empty even no '@' tag
+                                                                                       // was encountered in comment. But it cannot be the case for COMPILER_PARSER
+                                                                                       // and so is enough as it is only this parser which signals the missing tag warnings...
+                                                                                       this.inherited = this.astPtr==-1;
+                                                                                       if (this.kind == DOM_PARSER) {
+                                                                                               valid = parseTag();
+                                                                                       } else {
+                                                                                               valid = true;
+                                                                                       }
+                                                                               } else if (CharOperation.equals(tag, TAG_PARAM)) {
+                                                                                       valid = parseParam();
+                                                                               } else if (CharOperation.equals(tag, TAG_EXCEPTION)) {
+                                                                                       valid = parseThrows(false);
+                                                                               } else if (CharOperation.equals(tag, TAG_SEE)) {
+                                                                                       if (this.inlineTagStarted) {
+                                                                                               // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
+                                                                                               // Cannot have @see inside inline comment
+                                                                                               valid = false;
+                                                                                               if (this.sourceParser != null)
+                                                                                                       this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
+                                                                                       } else {
+                                                                                               valid = parseSee(false);
+                                                                                       }
+                                                                               } else if (CharOperation.equals(tag, TAG_LINK)) {
+                                                                                       if (this.inlineTagStarted) {
+                                                                                               valid = parseSee(false);
+                                                                                       } else {
+                                                                                               // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
+                                                                                               // Cannot have @link outside inline comment
+                                                                                               valid = false;
+                                                                                               if (this.sourceParser != null)
+                                                                                                       this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
+                                                                                       }
+                                                                               } else if (CharOperation.equals(tag, TAG_LINKPLAIN)) {
+                                                                                       if (this.inlineTagStarted) {
+                                                                                               valid = parseSee(true);
+                                                                                       } else {
+                                                                                               valid = parseTag();
+                                                                                       }
+                                                                               } else {
+                                                                                       valid = parseTag();
+                                                                               }
+                                                                               break;
+                                                                       case TerminalTokens.TokenNamereturn :
+                                                                               valid = parseReturn();
+                                                                               // verify characters after return tag (we're expecting text description)
+                                                                               if(!verifyCharsAfterReturnTag(this.index)) {
+                                                                                       if (this.sourceParser != null) {
+                                                                                               int end = this.starPosition == -1 || this.lineEnd<this.starPosition ? this.lineEnd : this.starPosition;
+                                                                                               this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, end);
+                                                                                       }
+                                                                               }
+                                                                               break;
+                                                                       case TerminalTokens.TokenNamethrows :
+                                                                               valid = parseThrows(true);
+                                                                               break;
+                                                                       default:
+                                                                               if (this.kind == DOM_PARSER) {
+                                                                                       switch (token) {
+                                                                                               case TerminalTokens.TokenNameabstract:
+                                                                                               case TerminalTokens.TokenNameassert:
+                                                                                               case TerminalTokens.TokenNameboolean:
+                                                                                               case TerminalTokens.TokenNamebreak:
+                                                                                               case TerminalTokens.TokenNamebyte:
+                                                                                               case TerminalTokens.TokenNamecase:
+                                                                                               case TerminalTokens.TokenNamecatch:
+                                                                                               case TerminalTokens.TokenNamechar:
+                                                                                               case TerminalTokens.TokenNameclass:
+                                                                                               case TerminalTokens.TokenNamecontinue:
+                                                                                               case TerminalTokens.TokenNamedefault:
+                                                                                               case TerminalTokens.TokenNamedo:
+                                                                                               case TerminalTokens.TokenNamedouble:
+                                                                                               case TerminalTokens.TokenNameelse:
+                                                                                               case TerminalTokens.TokenNameextends:
+                                                                                               case TerminalTokens.TokenNamefalse:
+                                                                                               case TerminalTokens.TokenNamefinal:
+                                                                                               case TerminalTokens.TokenNamefinally:
+                                                                                               case TerminalTokens.TokenNamefloat:
+                                                                                               case TerminalTokens.TokenNamefor:
+                                                                                               case TerminalTokens.TokenNameif:
+                                                                                               case TerminalTokens.TokenNameimplements:
+                                                                                               case TerminalTokens.TokenNameimport:
+                                                                                               case TerminalTokens.TokenNameinstanceof:
+                                                                                               case TerminalTokens.TokenNameint:
+                                                                                               case TerminalTokens.TokenNameinterface:
+                                                                                               case TerminalTokens.TokenNamelong:
+                                                                                               case TerminalTokens.TokenNamenative:
+                                                                                               case TerminalTokens.TokenNamenew:
+                                                                                               case TerminalTokens.TokenNamenull:
+                                                                                               case TerminalTokens.TokenNamepackage:
+                                                                                               case TerminalTokens.TokenNameprivate:
+                                                                                               case TerminalTokens.TokenNameprotected:
+                                                                                               case TerminalTokens.TokenNamepublic:
+                                                                                               case TerminalTokens.TokenNameshort:
+                                                                                               case TerminalTokens.TokenNamestatic:
+                                                                                               case TerminalTokens.TokenNamestrictfp:
+                                                                                               case TerminalTokens.TokenNamesuper:
+                                                                                               case TerminalTokens.TokenNameswitch:
+                                                                                               case TerminalTokens.TokenNamesynchronized:
+                                                                                               case TerminalTokens.TokenNamethis:
+                                                                                               case TerminalTokens.TokenNamethrow:
+                                                                                               case TerminalTokens.TokenNametransient:
+                                                                                               case TerminalTokens.TokenNametrue:
+                                                                                               case TerminalTokens.TokenNametry:
+                                                                                               case TerminalTokens.TokenNamevoid:
+                                                                                               case TerminalTokens.TokenNamevolatile:
+                                                                                               case TerminalTokens.TokenNamewhile:
+                                                                                                       valid = parseTag();
+                                                                                                       break;
+                                                                                       }
+                                                                               }
+                                                               }
+                                                               this.textStart = this.index;
+                                                               if (!valid) {
+                                                                       // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
+                                                                       // do not stop the inline tag when error is encountered to get text after
+                                                                       validComment = false;
+                                                                       // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
+                                                                       // for DOM AST node, store tag as text in case of invalid syntax
+                                                                       if (this.kind == DOM_PARSER) {
+                                                                               parseTag();
+                                                                               this.textStart = this.tagSourceEnd+1;
+                                                                               invalidTagLineEnd  = this.lineEnd;
+                                                                       }
+                                                               }
+                                                       } catch (InvalidInputException e) {
+                                                               consumeToken();
+                                                       }
+                                               }
+                                               break;
+                                       case '\r':
+                                       case '\n':
+                                               if (this.lineStarted && this.textStart < previousPosition) {
+                                                       pushText(this.textStart, previousPosition);
+                                               }
+                                               this.lineStarted = false;
+                                               // Fix bug 51650
+                                               this.textStart = -1;
+                                               break;
+                                       case '}' :
+                                               if (this.inlineTagStarted) {
+                                                       if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
+                                                               pushText(this.textStart, previousPosition);
+                                                       }
+                                                       if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
+                                                       this.textStart = this.index;
+                                                       this.inlineTagStarted = false;
+                                               } else {
+                                                       if (!this.lineStarted) {
+                                                               this.textStart = previousPosition;
+                                                       }
+                                               }
+                                               this.lineStarted = true;
+                                               break;
+                                       case '{' :
+                                               if (this.inlineTagStarted) {
+                                                       this.inlineTagStarted = false;
+                                                       // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
+                                                       // Cannot have opening brace in inline comment
+                                                       if (this.sourceParser != null) {
+                                                               int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
+                                                               this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
+                                                       }
+                                                       if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
+                                                               pushText(this.textStart, previousPosition);
+                                                       }
+                                                       if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
+                                               }
+                                               if (!this.lineStarted) {
+                                                       this.textStart = previousPosition;
+                                               }
+                                               this.lineStarted = true;
+                                               this.inlineTagStart = previousPosition;
+                                               break;
+                                       case '*' :
+                                       case '\u000c' : /* FORM FEED               */
+                                       case ' ' :                      /* SPACE                   */
+                                       case '\t' :                     /* HORIZONTAL TABULATION   */
+                                               // do nothing for space or '*' characters
+                                               break;
+                                       default :
+                                               if (!this.lineStarted) {
+                                                       this.textStart = previousPosition;
+                                               }
+                                               this.lineStarted = true;
+                                               break;
+                               }
+                       }
+                       // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
+                       // Cannot leave comment inside inline comment
+                       if (this.inlineTagStarted) {
+                               this.inlineTagStarted = false;
+                               if (this.sourceParser != null) {
+                                       int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
+                                       if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
+                                       this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
+                               }
+                               if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
+                                       pushText(this.textStart, previousPosition);
+                               }
+                               if (this.kind == DOM_PARSER) {
+                                       refreshInlineTagPosition(previousPosition);
+                               }
+                       } else if (this.lineStarted && this.textStart < previousPosition) {
+                               pushText(this.textStart, previousPosition);
+                       }
+                       updateDocComment();
+               } catch (Exception ex) {
+                       validComment = false;
+               }
+               return validComment;
+       }
+
+       private void consumeToken() {
+               this.currentTokenType = -1; // flush token cache
+               updateLineEnd();
+       }
+
+       protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
+       protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
+       protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
+       protected Object createReturnStatement() { return null; }
+       protected abstract Object createTypeReference(int primitiveToken);
+       
+       private int getEndPosition() {
+               if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
+                       return this.lineEnd;
+               } else {
+                       return this.scanner.getCurrentTokenEndPosition();
+               }
+       }
+
+       /*
+        * Search the source position corresponding to the end of a given line number.
+        * Warning: returned position is 1-based index!
+        * @see Scanner#getLineEnd(int) We cannot directly use this method
+        * when linePtr field is not initialized.
+        *
+       private int getLineEnd(int lineNumber) {
+       
+               if (this.scanner.linePtr != -1) {
+                       return this.scanner.getLineEnd(lineNumber);
+               }
+               if (this.lineEnds == null) 
+                       return -1;
+               if (lineNumber > this.lineEnds.length+1) 
+                       return -1;
+               if (lineNumber <= 0) 
+                       return -1;
+               if (lineNumber == this.lineEnds.length + 1) 
+                       return this.scanner.eofPosition;
+               return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
+       }
+       */
+
+       /**
+        * Search the line number corresponding to a specific position.
+        * Warning: returned position is 1-based index!
+        * @see Scanner#getLineNumber(int) We cannot directly use this method
+        * when linePtr field is not initialized.
+        */
+       private int getLineNumber(int position) {
+       
+               if (this.scanner.linePtr != -1) {
+                       return this.scanner.getLineNumber(position);
+               }
+               if (this.lineEnds == null)
+                       return 1;
+               int length = this.lineEnds.length;
+               if (length == 0)
+                       return 1;
+               int g = 0, d = length - 1;
+               int m = 0;
+               while (g <= d) {
+                       m = (g + d) /2;
+                       if (position < this.lineEnds[m]) {
+                               d = m-1;
+                       } else if (position > this.lineEnds[m]) {
+                               g = m+1;
+                       } else {
+                               return m + 1;
+                       }
+               }
+               if (position < this.lineEnds[m]) {
+                       return m+1;
+               }
+               return m+2;
+       }
+
+       /*
+        * Parse argument in @see tag method reference
+        */
+       private Object parseArguments(Object receiver) throws InvalidInputException {
+
+               // Init
+               int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
+               int iToken = 0;
+               char[] argName = null;
+               List arguments = new ArrayList(10);
+               int start = this.scanner.getCurrentTokenStartPosition();
+               
+               // Parse arguments declaration if method reference
+               nextArg : while (this.index < this.scanner.eofPosition) {
+
+                       // Read argument type reference
+                       Object typeRef;
+                       try {
+                               typeRef = parseQualifiedName(false);
+                       } catch (InvalidInputException e) {
+                               break nextArg;
+                       }
+                       boolean firstArg = modulo == 0;
+                       if (firstArg) { // verify position
+                               if (iToken != 0)
+                                       break nextArg;
+                       } else if ((iToken % modulo) != 0) {
+                                       break nextArg;
+                       }
+                       if (typeRef == null) {
+                               if (firstArg && this.currentTokenType == TerminalTokens.TokenNameRPAREN) {
+                                       // verify characters after arguments declaration (expecting white space or end comment)
+                                       if (!verifySpaceOrEndComment()) {
+                                               int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
+                                               if (this.source[end]=='\n') end--;
+                                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
+                                               return null;
+                                       }
+                                       this.lineStarted = true;
+                                       return createMethodReference(receiver, null);
+                               }
+                               break nextArg;
+                       }
+                       iToken++;
+
+                       // Read possible array declaration
+                       int dim = 0;
+                       long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
+                       if (readToken() == TerminalTokens.TokenNameLBRACKET) {
+                               int dimStart = this.scanner.getCurrentTokenStartPosition();
+                               while (readToken() == TerminalTokens.TokenNameLBRACKET) {
+                                       consumeToken();
+                                       if (readToken() != TerminalTokens.TokenNameRBRACKET) {
+                                               break nextArg;
+                                       }
+                                       consumeToken();
+                                       dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
+                               }
+                       }
+
+                       // Read argument name
+                       long argNamePos = -1;
+                       if (readToken() == TerminalTokens.TokenNameIdentifier) {
+                               consumeToken();
+                               if (firstArg) { // verify position
+                                       if (iToken != 1)
+                                               break nextArg;
+                               } else if ((iToken % modulo) != 1) {
+                                               break nextArg;
+                               }
+                               if (argName == null) { // verify that all arguments name are declared
+                                       if (!firstArg) {
+                                               break nextArg;
+                                       }
+                               }
+                               argName = this.scanner.getCurrentIdentifierSource();
+                               argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
+                               iToken++;
+                       } else if (argName != null) { // verify that no argument name is declared
+                               break nextArg;
+                       }
+                       
+                       // Verify token position
+                       if (firstArg) {
+                               modulo = iToken + 1;
+                       } else {
+                               if ((iToken % modulo) != (modulo - 1)) {
+                                       break nextArg;
+                               }
+                       }
+
+                       // Read separator or end arguments declaration
+                       int token = readToken();
+                       char[] name = argName == null ? new char[0] : argName;
+                       if (token == TerminalTokens.TokenNameCOMMA) {
+                               // Create new argument
+                               Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
+                               arguments.add(argument);
+                               consumeToken();
+                               iToken++;
+                       } else if (token == TerminalTokens.TokenNameRPAREN) {
+                               // verify characters after arguments declaration (expecting white space or end comment)
+                               if (!verifySpaceOrEndComment()) {
+                                       int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
+                                       if (this.source[end]=='\n') end--;
+                                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
+                                       return null;
+                               }
+                               // Create new argument
+                               Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
+                               arguments.add(argument);
+                               consumeToken();
+                               return createMethodReference(receiver, arguments);
+                       } else {
+                               break nextArg;
+                       }
+               }
+
+               // Something wrong happened => Invalid input
+               throw new InvalidInputException();
+       }
+
+       /*
+        * Parse an URL link reference in @see tag
+        */
+       private boolean parseHref() throws InvalidInputException {
+               int start = this.scanner.getCurrentTokenStartPosition();
+               if (Character.toLowerCase(readChar()) == 'a') {
+                       this.scanner.currentPosition = this.index;
+                       if (readToken() == TerminalTokens.TokenNameIdentifier) {
+                               this.currentTokenType = -1; // do not update line end
+                               try {
+                                       if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
+                                               readToken() == TerminalTokens.TokenNameEQUAL) {
+                                               this.currentTokenType = -1; // do not update line end
+                                               if (readToken() == TerminalTokens.TokenNameStringLiteral) {
+                                                       this.currentTokenType = -1; // do not update line end
+                                                       // Skip all characters after string literal until closing '>' (see bug 68726)
+                                                       while (this.index <= this.lineEnd && readToken() != TerminalTokens.TokenNameGREATER) {
+                                                               this.currentTokenType = -1; // do not update line end
+                                                       }
+                                                       if (this.currentTokenType == TerminalTokens.TokenNameGREATER) {
+                                                               consumeToken(); // update line end as new lines are allowed in URL description
+                                                               while (readToken() != TerminalTokens.TokenNameLESS) {
+                                                                       if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@') {
+                                                                               // Reset position: we want to rescan last token
+                                                                               this.index = this.tokenPreviousPosition;
+                                                                               this.scanner.currentPosition = this.tokenPreviousPosition;
+                                                                               this.currentTokenType = -1;
+                                                                               // Signal syntax error
+                                                                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
+                                                                               return false;
+                                                                       }
+                                                                       consumeToken();
+                                                               }
+                                                               this.currentTokenType = -1; // do not update line end
+                                                               if (readChar() == '/') {
+                                                                       if (Character.toLowerCase(readChar()) == 'a') {
+                                                                               if (readChar() == '>') {
+                                                                                       // Valid href
+                                                                                       return true;
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               } catch (InvalidInputException ex) {
+                                       // Do nothing as we want to keep positions for error message
+                               }
+                       }
+               }
+               // Reset position: we want to rescan last token
+               this.index = this.tokenPreviousPosition;
+               this.scanner.currentPosition = this.tokenPreviousPosition;
+               this.currentTokenType = -1;
+               // Signal syntax error
+               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
+               return false;
+       }
+
+       /*
+        * Parse a method reference in @see tag
+        */
+       private Object parseMember(Object receiver) throws InvalidInputException {
+               // Init
+               this.identifierPtr = -1;
+               this.identifierLengthPtr = -1;
+               int start = this.scanner.getCurrentTokenStartPosition();
+               this.memberStart = start;
+
+               // Get member identifier
+               if (readToken() == TerminalTokens.TokenNameIdentifier) {
+                       consumeToken();
+                       pushIdentifier(true);
+                       // Look for next token to know whether it's a field or method reference
+                       int previousPosition = this.index;
+                       if (readToken() == TerminalTokens.TokenNameLPAREN) {
+                               consumeToken();
+                               start = this.scanner.getCurrentTokenStartPosition();
+                               try {
+                                       return parseArguments(receiver);
+                               } catch (InvalidInputException e) {
+                                       int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
+                                                       this.scanner.getCurrentTokenEndPosition() :
+                                                       this.scanner.getCurrentTokenStartPosition();
+                                       end = end < this.lineEnd ? end : this.lineEnd;
+                                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
+                               }
+                               return null;
+                       }
+
+                       // Reset position: we want to rescan last token
+                       this.index = previousPosition;
+                       this.scanner.currentPosition = previousPosition;
+                       this.currentTokenType = -1;
+
+                       // Verify character(s) after identifier (expecting space or end comment)
+                       if (!verifySpaceOrEndComment()) {
+                               int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
+                               if (this.source[end]=='\n') end--;
+                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
+                               return null;
+                       }
+                       return createFieldReference(receiver);
+               }
+               int end = getEndPosition() - 1;
+               end = start > end ? start : end;
+               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, end);
+               // Reset position: we want to rescan last token
+               this.index = this.tokenPreviousPosition;
+               this.scanner.currentPosition = this.tokenPreviousPosition;
+               this.currentTokenType = -1;
+               return null;
+       }
+
+       /*
+        * Parse @param tag declaration
+        */
+       protected boolean parseParam() {
+
+               // Store current token state
+               int start = this.tagSourceStart;
+               int end = this.tagSourceEnd;
+
+               try {
+                       // Push identifier next
+                       int token = readToken();
+                       switch (token) {
+                               case TerminalTokens.TokenNameIdentifier :
+                                       consumeToken();
+                                       return pushParamName();
+                               case TerminalTokens.TokenNameEOF :
+                                       break;
+                               default :
+                                       start = this.scanner.getCurrentTokenStartPosition();
+                                       end = getEndPosition();
+                                       if (end < start) start = this.tagSourceStart;
+                                       break;
+                       }
+               } catch (InvalidInputException e) {
+                       end = getEndPosition();
+               }
+
+               // Reset position to avoid missing tokens when new line was encountered
+               this.index = this.tokenPreviousPosition;
+               this.scanner.currentPosition = this.tokenPreviousPosition;
+               this.currentTokenType = -1;
+
+               // Report problem
+               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingParamName(start, end);
+               return false;
+       }
+
+       /*
+        * Parse a qualified name and built a type reference if the syntax is valid.
+        */
+       protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
+
+               // Reset identifier stack if requested
+               if (reset) {
+                       this.identifierPtr = -1;
+                       this.identifierLengthPtr = -1;
+               }
+
+               // Scan tokens
+               int primitiveToken = -1;
+               nextToken : for (int iToken = 0; ; iToken++) {
+                       int token = readToken();
+                       switch (token) {
+                               case TerminalTokens.TokenNameIdentifier :
+                                       if (((iToken % 2) > 0)) { // identifiers must be odd tokens
+                                               break nextToken;
+                                       }
+                                       pushIdentifier(iToken == 0);
+                                       consumeToken();
+                                       break;
+
+                               case TerminalTokens.TokenNameDOT :
+                                       if ((iToken % 2) == 0) { // dots must be even tokens
+                                               throw new InvalidInputException();
+                                       }
+                                       consumeToken();
+                                       break;
+
+                               case TerminalTokens.TokenNamevoid :
+                               case TerminalTokens.TokenNameboolean :
+                               case TerminalTokens.TokenNamebyte :
+                               case TerminalTokens.TokenNamechar :
+                               case TerminalTokens.TokenNamedouble :
+                               case TerminalTokens.TokenNamefloat :
+                               case TerminalTokens.TokenNameint :
+                               case TerminalTokens.TokenNamelong :
+                               case TerminalTokens.TokenNameshort :
+                                       if (iToken > 0) {
+                                               throw new InvalidInputException();
+                                       }
+                                       pushIdentifier(true);
+                                       primitiveToken = token;
+                                       consumeToken();
+                                       break nextToken;
+
+                               default :
+                                       if (iToken == 0) {
+                                               return null;
+                                       }
+                                       if ((iToken % 2) == 0) { // cannot leave on a dot
+                                               // Reset position: we want to rescan last token
+                                               if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
+                                                       this.index = this.tokenPreviousPosition;
+                                                       this.scanner.currentPosition = this.tokenPreviousPosition;
+                                                       this.currentTokenType = -1;
+                                               }
+                                               throw new InvalidInputException();
+                                       }
+                                       break nextToken;
+                       }
+               }
+               // Reset position: we want to rescan last token
+               if (this.currentTokenType != -1) {
+                       this.index = this.tokenPreviousPosition;
+                       this.scanner.currentPosition = this.tokenPreviousPosition;
+                       this.currentTokenType = -1;
+               }
+               this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
+               return createTypeReference(primitiveToken);
+       }
+
+       /*
+        * Parse a reference in @see tag
+        */
+       protected boolean parseReference(boolean plain) throws InvalidInputException {
+               Object typeRef = null;
+               Object reference = null;
+               int previousPosition = -1;
+               int typeRefStartPosition = -1;
+               nextToken : while (this.index < this.scanner.eofPosition) {
+                       previousPosition = this.index;
+                       int token = readToken();
+                       switch (token) {
+                               case TerminalTokens.TokenNameStringLiteral : // @see "string"
+                                       int start = this.scanner.getCurrentTokenStartPosition();
+                                       consumeToken();
+                                       // If typeRef != null we may raise a warning here to let user know there's an unused reference...
+                                       // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
+                                       if (typeRef != null) {
+                                               start = this.tagSourceEnd+1;
+                                               previousPosition = start;
+                                               typeRef = null;
+                                       }
+                                       // verify end line (expecting empty or end comment)
+                                       if (verifyEndLine(previousPosition)) {
+                                               return true;
+                                       }
+                                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
+                                       return false;
+                               case TerminalTokens.TokenNameLESS : // @see "<a href="URL#Value">label</a>
+                                       consumeToken();
+                                       start = this.scanner.getCurrentTokenStartPosition();
+                                       if (parseHref()) {
+                                               consumeToken();
+                                               // If typeRef != null we may raise a warning here to let user know there's an unused reference...
+                                               // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
+                                               if (typeRef != null) {
+                                                       start = this.tagSourceEnd+1;
+                                                       previousPosition = start;
+                                                       typeRef = null;
+                                               }
+                                               // verify end line (expecting empty or end comment)
+                                               if (verifyEndLine(previousPosition)) {
+                                                       return true;
+                                               }
+                                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
+                                       }
+                                       return false;
+                               case TerminalTokens.TokenNameERROR :
+                                       if (this.scanner.currentCharacter == '#') { // @see ...#member
+                                               consumeToken();
+                                               reference = parseMember(typeRef);
+                                               if (reference != null) {
+                                                       return pushSeeRef(reference, plain);
+                                               }
+                                               return false;
+                                       }
+                                       break nextToken;
+                               case TerminalTokens.TokenNameIdentifier :
+                                       if (typeRef == null) {
+                                               typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
+                                               typeRef = parseQualifiedName(true);
+                                               break;
+                                       }
+                                       break nextToken;
+                               default :
+                                       break nextToken;
+                       }
+               }
+               
+               // Verify that we got a reference
+               if (reference == null) reference = typeRef;
+               if (reference == null) {
+                       this.index = this.tokenPreviousPosition;
+                       this.scanner.currentPosition = this.tokenPreviousPosition;
+                       this.currentTokenType = -1;
+                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingSeeReference(this.tagSourceStart, this.tagSourceEnd);
+                       return false;
+               }
+
+               // Reset position at the end of type reference
+               this.index = this.lastIdentifierEndPosition+1;
+               this.scanner.currentPosition = this.index;
+               this.currentTokenType = -1;
+
+               // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
+               // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
+               char ch = peekChar();
+               if (ch == '(') {
+                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(typeRefStartPosition, this.lineEnd);
+                       return false;
+               }
+
+               // Verify that we get white space after reference
+               if (!verifySpaceOrEndComment()) {
+                       this.index = this.tokenPreviousPosition;
+                       this.scanner.currentPosition = this.tokenPreviousPosition;
+                       this.currentTokenType = -1;
+                       int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
+                       if (this.source[end]=='\n') end--;
+                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
+                       return false;
+               }
+               
+               // Everything is OK, store reference
+               return pushSeeRef(reference, plain);
+       }
+
+       /*
+        * Parse @return tag declaration
+        */
+       protected abstract boolean parseReturn();
+
+       /*
+        * Parse @see tag declaration
+        */
+       protected boolean parseSee(boolean plain) {
+               int start = this.scanner.currentPosition;
+               try {
+                       return parseReference(plain);
+               } catch (InvalidInputException ex) {
+                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, getEndPosition());
+               }
+               // Reset position to avoid missing tokens when new line was encountered
+               this.index = this.tokenPreviousPosition;
+               this.scanner.currentPosition = this.tokenPreviousPosition;
+               this.currentTokenType = -1;
+               return false;
+       }
+
+       /*
+        * Parse @return tag declaration
+        */
+       protected abstract boolean parseTag();
+
+       /*
+        * Parse @throws tag declaration
+        */
+       protected boolean parseThrows(boolean real) {
+               int start = this.scanner.currentPosition;
+               try {
+                       Object typeRef = parseQualifiedName(true);
+                       if (typeRef == null) {
+                               if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd);
+                       } else {
+                               return pushThrowName(typeRef, real);
+                       }
+               } catch (InvalidInputException ex) {
+                       if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getEndPosition());
+               }
+               return false;
+       }
+
+       /*
+        * Return current character without move index position.
+        */
+       private char peekChar() {
+               int idx = this.index;
+               char c = this.source[idx++];
+               if (c == '\\' && this.source[idx] == 'u') {
+                       int c1, c2, c3, c4;
+                       idx++;
+                       while (this.source[idx] == 'u')
+                               idx++;
+                       if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
+                                       || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
+                                       || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
+                               c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
+                       }
+               }
+               return c;
+       }
+
+       /*
+        * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
+        */
+       protected void pushIdentifier(boolean newLength) {
+
+               int stackLength = this.identifierStack.length;
+               if (++this.identifierPtr >= stackLength) {
+                       System.arraycopy(
+                               this.identifierStack, 0,
+                               this.identifierStack = new char[stackLength + 10][], 0,
+                               stackLength);
+                       System.arraycopy(
+                               this.identifierPositionStack, 0,
+                               this.identifierPositionStack = new long[stackLength + 10], 0,
+                               stackLength);
+               }
+               this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
+               this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
+
+               if (newLength) {
+                       stackLength = this.identifierLengthStack.length;
+                       if (++this.identifierLengthPtr >= stackLength) {
+                               System.arraycopy(
+                                       this.identifierLengthStack, 0,
+                                       this.identifierLengthStack = new int[stackLength + 10], 0,
+                                       stackLength);
+                       }
+                       this.identifierLengthStack[this.identifierLengthPtr] = 1;
+               } else {
+                       this.identifierLengthStack[this.identifierLengthPtr]++;
+               }
+       }
+
+       /*
+        * Add a new obj on top of the ast stack.
+        * If new length is required, then add also a new length in length stack.
+        */
+       protected void pushOnAstStack(Object node, boolean newLength) {
+
+               if (node == null) {
+                       this.astLengthStack[++this.astLengthPtr] = 0;
+                       return;
+               }
+
+               int stackLength = this.astStack.length;
+               if (++this.astPtr >= stackLength) {
+                       System.arraycopy(
+                               this.astStack, 0,
+                               this.astStack = new Object[stackLength + AstStackIncrement], 0,
+                               stackLength);
+                       this.astPtr = stackLength;
+               }
+               this.astStack[this.astPtr] = node;
+
+               if (newLength) {
+                       stackLength = this.astLengthStack.length;
+                       if (++this.astLengthPtr >= stackLength) {
+                               System.arraycopy(
+                                       this.astLengthStack, 0,
+                                       this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
+                                       stackLength);
+                       }
+                       this.astLengthStack[this.astLengthPtr] = 1;
+               } else {
+                       this.astLengthStack[this.astLengthPtr]++;
+               }
+       }
+
+       /*
+        * Push a param name in ast node stack.
+        */
+       protected abstract boolean pushParamName();
+
+       /*
+        * Push a reference statement in ast node stack.
+        */
+       protected abstract boolean pushSeeRef(Object statement, boolean plain);
+
+       /*
+        * Push a text element in ast node stack
+        */
+       protected abstract void pushText(int start, int end);
+
+       /*
+        * Push a throws type ref in ast node stack.
+        */
+       protected abstract boolean pushThrowName(Object typeRef, boolean real);
+
+       /*
+        * Read current character and move index position.
+        * Warning: scanner position is unchanged using this method!
+        */
+       protected char readChar() {
+       
+               char c = this.source[this.index++];
+               if (c == '\\' && this.source[this.index] == 'u') {
+                       int c1, c2, c3, c4;
+                       int pos = this.index;
+                       this.index++;
+                       while (this.source[this.index] == 'u')
+                               this.index++;
+                       if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
+                                       || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
+                                       || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
+                               c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
+                       } else {
+                               // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
+                               this.index = pos;
+                       }
+               }
+               return c;
+       }
+
+       /*
+        * Read token only if previous was consumed
+        */
+       private int readToken() throws InvalidInputException {
+               if (this.currentTokenType < 0) {
+                       this.tokenPreviousPosition = this.scanner.currentPosition;
+                       this.currentTokenType = this.scanner.getNextToken();
+                       if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
+                               this.lineStarted = false;
+                               while (this.currentTokenType == TerminalTokens.TokenNameMULTIPLY) {
+                                       this.currentTokenType = this.scanner.getNextToken();
+                               }
+                       }
+                       this.index = this.scanner.currentPosition;
+                       this.lineStarted = true; // after having read a token, line is obviously started...
+               }
+               return this.currentTokenType;
+       }
+
+       private int readTokenAndConsume() throws InvalidInputException {
+               int token = readToken();
+               consumeToken();
+               return token;
+       }
+       
+       /*
+        * Refresh start position and length of an inline tag.
+        */
+       protected void refreshInlineTagPosition(int previousPosition) {
+               // do nothing by default
+       }
+
+       public String toString() {
+               StringBuffer buffer = new StringBuffer();
+               int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
+               int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
+               if (startPos == this.source.length)
+                       return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
+               if (endPos > this.source.length)
+                       return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
+       
+               char front[] = new char[startPos];
+               System.arraycopy(this.source, 0, front, 0, startPos);
+       
+               int middleLength = (endPos - 1) - startPos + 1;
+               char middle[];
+               if (middleLength > -1) {
+                       middle = new char[middleLength];
+                       System.arraycopy(
+                               this.source, 
+                               startPos, 
+                               middle, 
+                               0, 
+                               middleLength);
+               } else {
+                       middle = CharOperation.NO_CHAR;
+               }
+               
+               char end[] = new char[this.source.length - (endPos - 1)];
+               System.arraycopy(
+                       this.source, 
+                       (endPos - 1) + 1, 
+                       end, 
+                       0, 
+                       this.source.length - (endPos - 1) - 1);
+               
+               buffer.append(front);
+               if (this.scanner.currentPosition<this.index) {
+                       buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
+               } else {
+                       buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
+               }
+               buffer.append(middle);
+               if (this.scanner.currentPosition<this.index) {
+                       buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
+               } else {
+                       buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
+               }
+               buffer.append(end);
+
+               return buffer.toString();
+       }
+
+       /*
+        * Update 
+        */
+       protected abstract void updateDocComment();
+
+       /*
+        * Update line end
+        */
+       protected void updateLineEnd() {
+               while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
+                       if (this.linePtr < this.lastLinePtr) {
+                               this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
+                       } else {
+                               this.lineEnd = this.endComment;
+                               return;
+                       }
+               }
+       }
+
+       /*
+        * Verify that end of the line only contains space characters or end of comment.
+        * Note that end of comment may be preceeding by several contiguous '*' chars.
+        */
+       private boolean verifyEndLine(int textPosition) {
+               int startPosition = this.index;
+               int previousPosition = this.index;
+               this.starPosition = -1;
+               char ch = readChar();
+               nextChar: while (true) {
+                       switch (ch) {
+                               case '\r':
+                               case '\n':
+                                       if (this.kind == DOM_PARSER) {
+                                               parseTag();
+                                               pushText(textPosition, previousPosition);
+                                       }
+                                       this.index = previousPosition;
+                                       return true;
+                               case '\u000c' : /* FORM FEED               */
+                               case ' ' :                      /* SPACE                   */
+                               case '\t' :                     /* HORIZONTAL TABULATION   */
+                                       if (this.starPosition >= 0) break nextChar;
+                                       break;
+                               case '*':
+                                       this.starPosition = previousPosition;
+                                       break;
+                               case '/':
+                                       if (this.starPosition >= textPosition) {
+                                               if (this.kind == DOM_PARSER) {
+                                                       parseTag();
+                                                       pushText(textPosition, this.starPosition);
+                                               }
+                                               return true;
+                                       }
+                               default :
+                                       // leave loop
+                                       break nextChar;
+                               
+                       }
+                       previousPosition = this.index;
+                       ch = readChar();
+               }
+               this.index = startPosition;
+               return false;
+       }
+
+       /*
+        * Verify that some text exists after a @return tag. Text must be different than
+        * end of comment which may be preceeding by several '*' chars.
+        */
+       private boolean verifyCharsAfterReturnTag(int startPosition) {
+               // Whitespace or inline tag closing brace
+               int previousPosition = this.index;
+               char ch = readChar();
+               boolean malformed = true;
+               while (Character.isWhitespace(ch)) {
+                       malformed = false;
+                       previousPosition = this.index;
+                       ch = readChar();        
+               }
+               // End of comment
+               this.starPosition = -1;
+               nextChar: while (this.index<this.source.length) {
+                       switch (ch) {
+                               case '*':
+                                       // valid whatever the number of star before last '/'
+                                       this.starPosition = previousPosition;
+                                       break;
+                               case '/':
+                                       if (this.starPosition >= startPosition) { // valid only if a star was previous character
+                                               return false;
+                                       }
+                               default :
+                                       // valid if any other character is encountered, even white spaces
+                                       this.index = startPosition;
+                                       return !malformed;
+                               
+                       }
+                       previousPosition = this.index;
+                       ch = readChar();
+               }
+               this.index = startPosition;
+               return false;
+       }
+
+       /*
+        * Verify characters after a name matches one of following conditions:
+        *      1- first character is a white space
+        *      2- first character is a closing brace *and* we're currently parsing an inline tag
+        *      3- are the end of comment (several contiguous star ('*') characters may be
+        *          found before the last slash ('/') character).
+        */
+       private boolean verifySpaceOrEndComment() {
+               int startPosition = this.index;
+               // Whitespace or inline tag closing brace
+               char ch = peekChar();
+               switch (ch) {
+                       case '}':
+                               return this.inlineTagStarted;
+                       default:
+                               if (Character.isWhitespace(ch)) {
+                                       return true;
+                               }
+               }
+               // End of comment
+               int previousPosition = this.index;
+               this.starPosition = -1;
+               ch = readChar();
+               nextChar: while (this.index<this.source.length) {
+                       switch (ch) {
+                               case '*':
+                                       // valid whatever the number of star before last '/'
+                                       this.starPosition = previousPosition;
+                                       break;
+                               case '/':
+                                       if (this.starPosition >= startPosition) { // valid only if a star was previous character
+                                               return true;
+                                       }
+                               default :
+                                       // invalid whatever other character, even white spaces
+                                       this.index = startPosition;
+                                       return false;
+                               
+                       }
+                       previousPosition = this.index;
+                       ch = readChar();
+               }
+               this.index = startPosition;
+               return false;
+       }
+}