added -J option to preserve unmodified files in preexisting jarfile
[org.ibex.tool.git] / src / org / eclipse / jdt / internal / compiler / parser / AbstractCommentParser.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.parser;
12
13 import java.util.ArrayList;
14 import java.util.List;
15
16 import org.eclipse.jdt.core.compiler.CharOperation;
17 import org.eclipse.jdt.core.compiler.InvalidInputException;
18 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
19
20 /**
21  * Parser specialized for decoding javadoc comments
22  */
23 public abstract class AbstractCommentParser {
24
25         // recognized tags
26         public static final char[] TAG_DEPRECATED = "deprecated".toCharArray(); //$NON-NLS-1$
27         public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
28         public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
29         public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
30         public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
31         public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
32         public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
33         public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
34         public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
35         public static final char[] TAG_VALUE = "value".toCharArray(); //$NON-NLS-1$
36
37         // tags value
38         public static final int NO_TAG_VALUE = 0;
39         public static final int TAG_DEPRECATED_VALUE = 1;
40         public static final int TAG_PARAM_VALUE = 2;
41         public static final int TAG_RETURN_VALUE = 3;
42         public static final int TAG_THROWS_VALUE = 4;
43         public static final int TAG_EXCEPTION_VALUE = 5;
44         public static final int TAG_SEE_VALUE = 6;
45         public static final int TAG_LINK_VALUE = 7;
46         public static final int TAG_LINKPLAIN_VALUE = 8;
47         public static final int TAG_INHERITDOC_VALUE = 9;
48         public static final int TAG_VALUE_VALUE = 10;
49         public static final int TAG_OTHERS_VALUE = 11;
50         protected int tagValue = NO_TAG_VALUE;
51         
52         // tags expected positions
53         public final static int ORDERED_TAGS_NUMBER = 3;
54         public final static int PARAM_TAG_EXPECTED_ORDER = 0;
55         public final static int THROWS_TAG_EXPECTED_ORDER = 1;
56         public final static int SEE_TAG_EXPECTED_ORDER = 2;
57         
58         // Kind of comment parser
59         public final static int COMPIL_PARSER = 0x00000001;
60         public final static int DOM_PARSER = 0x00000002;
61         
62         // Parse infos
63         public Scanner scanner;
64         public char[] source;
65         protected Parser sourceParser;
66         private int currentTokenType = -1;
67         
68         // Options
69         public boolean checkDocComment = false;
70         public boolean reportProblems;
71         protected boolean jdk15;
72         
73         // Results
74         protected boolean inherited, deprecated;
75         protected Object returnStatement;
76         
77         // Positions
78         protected int index, endComment, lineEnd;
79         protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
80         protected int textStart, memberStart;
81         protected int tagSourceStart, tagSourceEnd;
82         protected int inlineTagStart;
83         protected int[] lineEnds;
84         
85         // Flags
86         protected boolean lineStarted = false, inlineTagStarted = false;
87         protected int kind;
88         
89         // Line pointers
90         private int linePtr, lastLinePtr;
91         
92         // Identifier stack
93         protected int identifierPtr;
94         protected char[][] identifierStack;
95         protected int identifierLengthPtr;
96         protected int[] identifierLengthStack;
97         protected long[] identifierPositionStack;
98         // Ast stack
99         protected static int AstStackIncrement = 10;
100         protected int astPtr;
101         protected Object[] astStack;
102         protected int astLengthPtr;
103         protected int[] astLengthStack;
104
105         protected AbstractCommentParser(Parser sourceParser) {
106                 this.sourceParser = sourceParser;
107                 this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/);
108                 this.identifierStack = new char[20][];
109                 this.identifierPositionStack = new long[20];
110                 this.identifierLengthStack = new int[10];
111                 this.astStack = new Object[30];
112                 this.astLengthStack = new int[20];
113                 this.reportProblems = sourceParser != null;
114         }
115
116         /* (non-Javadoc)
117          * Returns true if tag @deprecated is present in javadoc comment.
118          * 
119          * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
120          * slot for being consumed later on.
121          */
122         protected boolean commentParse(int javadocStart, int javadocEnd) {
123
124                 boolean validComment = true;
125                 try {
126                         // Init scanner position
127                         this.scanner.resetTo(javadocStart, javadocEnd);
128                         this.endComment = javadocEnd;
129                         this.index = javadocStart;
130                         readChar(); // starting '/'
131                         int previousPosition = this.index;
132                         readChar(); // first '*'
133                         char nextCharacter= readChar(); // second '*'
134                         
135                         // Init local variables
136                         this.astLengthPtr = -1;
137                         this.astPtr = -1;
138                         this.currentTokenType = -1;
139                         this.inlineTagStarted = false;
140                         this.inlineTagStart = -1;
141                         this.lineStarted = false;
142                         this.returnStatement = null;
143                         this.inherited = false;
144                         this.deprecated = false;
145                         this.linePtr = getLineNumber(javadocStart);
146                         this.lastLinePtr = getLineNumber(javadocEnd);
147                         this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
148                         this.textStart = -1;
149                         char previousChar = 0;
150                         int invalidTagLineEnd = -1;
151                         int invalidInlineTagLineEnd = -1;
152                         
153                         // Loop on each comment character
154                         while (this.index < this.endComment) {
155                                 previousPosition = this.index;
156                                 previousChar = nextCharacter;
157                                 
158                                 // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
159                                 if (this.index > (this.lineEnd+1)) {
160                                         updateLineEnd();
161                                 }
162                                 
163                                 // Read next char only if token was consumed
164                                 if (this.currentTokenType < 0) {
165                                         nextCharacter = readChar(); // consider unicodes
166                                 } else {
167                                         previousPosition = this.scanner.getCurrentTokenStartPosition();
168                                         switch (this.currentTokenType) {
169                                                 case TerminalTokens.TokenNameRBRACE:
170                                                         nextCharacter = '}';
171                                                         break;
172                                                 case TerminalTokens.TokenNameMULTIPLY:
173                                                         nextCharacter = '*';
174                                                         break;
175                                         default:
176                                                         nextCharacter = this.scanner.currentCharacter;
177                                         }
178                                         consumeToken();
179                                 }
180                         
181                                 if (this.index >= this.endComment) {
182                                         break;
183                                 }
184                                 
185                                 switch (nextCharacter) {
186                                         case '@' :
187                                                 // Start tag parsing only if we are on line beginning or at inline tag beginning
188                                                 if ((!this.lineStarted || previousChar == '{')) {
189                                                         this.lineStarted = true;
190                                                         if (this.inlineTagStarted) {
191                                                                 this.inlineTagStarted = false;
192                                                                 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
193                                                                 // Cannot have @ inside inline comment
194                                                                 if (this.reportProblems) {
195                                                                         int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
196                                                                         this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
197                                                                 }
198                                                                 validComment = false;
199                                                                 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
200                                                                         pushText(this.textStart, previousPosition);
201                                                                 }
202                                                                 if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
203                                                         }
204                                                         if (previousChar == '{') {
205                                                                 if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
206                                                                         pushText(this.textStart, this.inlineTagStart);
207                                                                 }
208                                                                 this.inlineTagStarted = true;
209                                                                 invalidInlineTagLineEnd = this.lineEnd;
210                                                         } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
211                                                                 pushText(this.textStart, invalidTagLineEnd);
212                                                         }
213                                                         this.scanner.resetTo(this.index, this.endComment);
214                                                         this.currentTokenType = -1; // flush token cache at line begin
215                                                         try {
216                                                                 if (!parseTag(previousPosition)) {
217                                                                         // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
218                                                                         // do not stop the inline tag when error is encountered to get text after
219                                                                         validComment = false;
220                                                                         // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
221                                                                         // for DOM AST node, store tag as text in case of invalid syntax
222                                                                         if (this.kind == DOM_PARSER) {
223                                                                                 createTag();
224                                                                                 this.textStart = this.tagSourceEnd+1;
225                                                                                 invalidTagLineEnd  = this.lineEnd;
226                                                                         }
227                                                                 }
228                                                         } catch (InvalidInputException e) {
229                                                                 consumeToken();
230                                                         }
231                                                 }
232                                                 break;
233                                         case '\r':
234                                         case '\n':
235                                                 if (this.lineStarted && this.textStart < previousPosition) {
236                                                         pushText(this.textStart, previousPosition);
237                                                 }
238                                                 this.lineStarted = false;
239                                                 // Fix bug 51650
240                                                 this.textStart = -1;
241                                                 break;
242                                         case '}' :
243                                                 if (this.inlineTagStarted) {
244                                                         if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
245                                                                 pushText(this.textStart, previousPosition);
246                                                         }
247                                                         if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
248                                                         this.textStart = this.index;
249                                                         this.inlineTagStarted = false;
250                                                 } else {
251                                                         if (!this.lineStarted) {
252                                                                 this.textStart = previousPosition;
253                                                         }
254                                                 }
255                                                 this.lineStarted = true;
256                                                 break;
257                                         case '{' :
258                                                 if (this.inlineTagStarted) {
259                                                         this.inlineTagStarted = false;
260                                                         // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
261                                                         // Cannot have opening brace in inline comment
262                                                         if (this.reportProblems) {
263                                                                 int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
264                                                                 this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
265                                                         }
266                                                         if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
267                                                                 pushText(this.textStart, previousPosition);
268                                                         }
269                                                         if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
270                                                 }
271                                                 if (!this.lineStarted) {
272                                                         this.textStart = previousPosition;
273                                                 }
274                                                 this.lineStarted = true;
275                                                 this.inlineTagStart = previousPosition;
276                                                 break;
277                                         case '*' :
278                                         case '\u000c' : /* FORM FEED               */
279                                         case ' ' :                      /* SPACE                   */
280                                         case '\t' :                     /* HORIZONTAL TABULATION   */
281                                                 // do nothing for space or '*' characters
282                                                 break;
283                                         default :
284                                                 if (!this.lineStarted) {
285                                                         this.textStart = previousPosition;
286                                                 }
287                                                 this.lineStarted = true;
288                                                 break;
289                                 }
290                         }
291                         // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
292                         // Cannot leave comment inside inline comment
293                         if (this.inlineTagStarted) {
294                                 this.inlineTagStarted = false;
295                                 if (this.reportProblems) {
296                                         int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
297                                         if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
298                                         this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
299                                 }
300                                 if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
301                                         pushText(this.textStart, previousPosition);
302                                 }
303                                 if (this.kind == DOM_PARSER) {
304                                         refreshInlineTagPosition(previousPosition);
305                                 }
306                         } else if (this.lineStarted && this.textStart < previousPosition) {
307                                 pushText(this.textStart, previousPosition);
308                         }
309                         updateDocComment();
310                 } catch (Exception ex) {
311                         validComment = false;
312                 }
313                 return validComment;
314         }
315
316         private void consumeToken() {
317                 this.currentTokenType = -1; // flush token cache
318                 updateLineEnd();
319         }
320
321         protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
322         protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
323         protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
324         protected Object createReturnStatement() { return null; }
325         protected abstract void createTag();
326         protected abstract Object createTypeReference(int primitiveToken);
327
328         private int getIndexPosition() {
329                 if (this.index > this.lineEnd) {
330                         return this.lineEnd;
331                 } else {
332                         return this.index-1;
333                 }
334         }
335
336         /**
337          * Search the line number corresponding to a specific position.
338          * Warning: returned position is 1-based index!
339          * @see Scanner#getLineNumber(int) We cannot directly use this method
340          * when linePtr field is not initialized.
341          */
342         private int getLineNumber(int position) {
343         
344                 if (this.scanner.linePtr != -1) {
345                         return this.scanner.getLineNumber(position);
346                 }
347                 if (this.lineEnds == null)
348                         return 1;
349                 int length = this.lineEnds.length;
350                 if (length == 0)
351                         return 1;
352                 int g = 0, d = length - 1;
353                 int m = 0;
354                 while (g <= d) {
355                         m = (g + d) /2;
356                         if (position < this.lineEnds[m]) {
357                                 d = m-1;
358                         } else if (position > this.lineEnds[m]) {
359                                 g = m+1;
360                         } else {
361                                 return m + 1;
362                         }
363                 }
364                 if (position < this.lineEnds[m]) {
365                         return m+1;
366                 }
367                 return m+2;
368         }
369
370         private int getTokenEndPosition() {
371                 if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
372                         return this.lineEnd;
373                 } else {
374                         return this.scanner.getCurrentTokenEndPosition();
375                 }
376         }
377         
378         /*
379          * Parse argument in @see tag method reference
380          */
381         private Object parseArguments(Object receiver) throws InvalidInputException {
382
383                 // Init
384                 int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
385                 int iToken = 0;
386                 char[] argName = null;
387                 List arguments = new ArrayList(10);
388                 int start = this.scanner.getCurrentTokenStartPosition();
389                 
390                 // Parse arguments declaration if method reference
391                 nextArg : while (this.index < this.scanner.eofPosition) {
392
393                         // Read argument type reference
394                         Object typeRef;
395                         try {
396                                 typeRef = parseQualifiedName(false);
397                         } catch (InvalidInputException e) {
398                                 break nextArg;
399                         }
400                         boolean firstArg = modulo == 0;
401                         if (firstArg) { // verify position
402                                 if (iToken != 0)
403                                         break nextArg;
404                         } else if ((iToken % modulo) != 0) {
405                                         break nextArg;
406                         }
407                         if (typeRef == null) {
408                                 if (firstArg && this.currentTokenType == TerminalTokens.TokenNameRPAREN) {
409                                         // verify characters after arguments declaration (expecting white space or end comment)
410                                         if (!verifySpaceOrEndComment()) {
411                                                 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
412                                                 if (this.source[end]=='\n') end--;
413                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
414                                                 return null;
415                                         }
416                                         this.lineStarted = true;
417                                         return createMethodReference(receiver, null);
418                                 }
419                                 break nextArg;
420                         }
421                         iToken++;
422
423                         // Read possible array declaration
424                         int dim = 0;
425                         long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
426                         if (readToken() == TerminalTokens.TokenNameLBRACKET) {
427                                 int dimStart = this.scanner.getCurrentTokenStartPosition();
428                                 while (readToken() == TerminalTokens.TokenNameLBRACKET) {
429                                         consumeToken();
430                                         if (readToken() != TerminalTokens.TokenNameRBRACKET) {
431                                                 break nextArg;
432                                         }
433                                         consumeToken();
434                                         dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
435                                 }
436                         }
437
438                         // Read argument name
439                         long argNamePos = -1;
440                         if (readToken() == TerminalTokens.TokenNameIdentifier) {
441                                 consumeToken();
442                                 if (firstArg) { // verify position
443                                         if (iToken != 1)
444                                                 break nextArg;
445                                 } else if ((iToken % modulo) != 1) {
446                                                 break nextArg;
447                                 }
448                                 if (argName == null) { // verify that all arguments name are declared
449                                         if (!firstArg) {
450                                                 break nextArg;
451                                         }
452                                 }
453                                 argName = this.scanner.getCurrentIdentifierSource();
454                                 argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
455                                 iToken++;
456                         } else if (argName != null) { // verify that no argument name is declared
457                                 break nextArg;
458                         }
459                         
460                         // Verify token position
461                         if (firstArg) {
462                                 modulo = iToken + 1;
463                         } else {
464                                 if ((iToken % modulo) != (modulo - 1)) {
465                                         break nextArg;
466                                 }
467                         }
468
469                         // Read separator or end arguments declaration
470                         int token = readToken();
471                         char[] name = argName == null ? new char[0] : argName;
472                         if (token == TerminalTokens.TokenNameCOMMA) {
473                                 // Create new argument
474                                 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
475                                 arguments.add(argument);
476                                 consumeToken();
477                                 iToken++;
478                         } else if (token == TerminalTokens.TokenNameRPAREN) {
479                                 // verify characters after arguments declaration (expecting white space or end comment)
480                                 if (!verifySpaceOrEndComment()) {
481                                         int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
482                                         if (this.source[end]=='\n') end--;
483                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
484                                         return null;
485                                 }
486                                 // Create new argument
487                                 Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
488                                 arguments.add(argument);
489                                 consumeToken();
490                                 return createMethodReference(receiver, arguments);
491                         } else {
492                                 break nextArg;
493                         }
494                 }
495
496                 // Something wrong happened => Invalid input
497                 throw new InvalidInputException();
498         }
499
500         /*
501          * Parse an URL link reference in @see tag
502          */
503         private boolean parseHref() throws InvalidInputException {
504                 int start = this.scanner.getCurrentTokenStartPosition();
505                 if (Character.toLowerCase(readChar()) == 'a') {
506                         this.scanner.currentPosition = this.index;
507                         if (readToken() == TerminalTokens.TokenNameIdentifier) {
508                                 consumeToken();
509                                 try {
510                                         if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
511                                                 readToken() == TerminalTokens.TokenNameEQUAL) {
512                                                 consumeToken();
513                                                 if (readToken() == TerminalTokens.TokenNameStringLiteral) {
514                                                         consumeToken();
515                                                         // Skip all characters after string literal until closing '>' (see bug 68726)
516                                                         while (readToken() != TerminalTokens.TokenNameGREATER) {
517                                                                 if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' ||
518                                                                         (this.inlineTagStarted && this.scanner.currentCharacter == '}')) {
519                                                                         // Reset position: we want to rescan last token
520                                                                         this.index = this.tokenPreviousPosition;
521                                                                         this.scanner.currentPosition = this.tokenPreviousPosition;
522                                                                         this.currentTokenType = -1;
523                                                                         // Signal syntax error
524                                                                         if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
525                                                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
526                                                                         }
527                                                                         return false;
528                                                                 }
529                                                                 this.currentTokenType = -1; // do not update line end
530                                                         }
531                                                         if (this.currentTokenType == TerminalTokens.TokenNameGREATER) {
532                                                                 consumeToken(); // update line end as new lines are allowed in URL description
533                                                                 while (readToken() != TerminalTokens.TokenNameLESS) {
534                                                                         if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' ||
535                                                                                 (this.inlineTagStarted && this.scanner.currentCharacter == '}')) {
536                                                                                 // Reset position: we want to rescan last token
537                                                                                 this.index = this.tokenPreviousPosition;
538                                                                                 this.scanner.currentPosition = this.tokenPreviousPosition;
539                                                                                 this.currentTokenType = -1;
540                                                                                 // Signal syntax error
541                                                                                 if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
542                                                                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
543                                                                                 }
544                                                                                 return false;
545                                                                         }
546                                                                         consumeToken();
547                                                                 }
548                                                                 consumeToken();
549                                                                 start = this.scanner.getCurrentTokenStartPosition();
550                                                                 if (readChar() == '/') {
551                                                                         if (Character.toLowerCase(readChar()) == 'a') {
552                                                                                 if (readChar() == '>') {
553                                                                                         // Valid href
554                                                                                         return true;
555                                                                                 }
556                                                                         }
557                                                                 }
558                                                         }
559                                                 }
560                                         }
561                                 } catch (InvalidInputException ex) {
562                                         // Do nothing as we want to keep positions for error message
563                                 }
564                         }
565                 }
566                 // Reset position: we want to rescan last token
567                 this.index = this.tokenPreviousPosition;
568                 this.scanner.currentPosition = this.tokenPreviousPosition;
569                 this.currentTokenType = -1;
570                 // Signal syntax error
571                 if (this.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
572                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
573                 }
574                 return false;
575         }
576
577         /*
578          * Parse a method reference in @see tag
579          */
580         private Object parseMember(Object receiver) throws InvalidInputException {
581                 // Init
582                 this.identifierPtr = -1;
583                 this.identifierLengthPtr = -1;
584                 int start = this.scanner.getCurrentTokenStartPosition();
585                 this.memberStart = start;
586         
587                 // Get member identifier
588                 if (readToken() == TerminalTokens.TokenNameIdentifier) {
589                         consumeToken();
590                         pushIdentifier(true);
591                         // Look for next token to know whether it's a field or method reference
592                         int previousPosition = this.index;
593                         if (readToken() == TerminalTokens.TokenNameLPAREN) {
594                                 consumeToken();
595                                 start = this.scanner.getCurrentTokenStartPosition();
596                                 try {
597                                         return parseArguments(receiver);
598                                 } catch (InvalidInputException e) {
599                                         int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
600                                                         this.scanner.getCurrentTokenEndPosition() :
601                                                         this.scanner.getCurrentTokenStartPosition();
602                                         end = end < this.lineEnd ? end : this.lineEnd;
603                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
604                                 }
605                                 return null;
606                         }
607         
608                         // Reset position: we want to rescan last token
609                         this.index = previousPosition;
610                         this.scanner.currentPosition = previousPosition;
611                         this.currentTokenType = -1;
612         
613                         // Verify character(s) after identifier (expecting space or end comment)
614                         if (!verifySpaceOrEndComment()) {
615                                 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
616                                 if (this.source[end]=='\n') end--;
617                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
618                                 return null;
619                         }
620                         return createFieldReference(receiver);
621                 }
622                 int end = getTokenEndPosition() - 1;
623                 end = start > end ? start : end;
624                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(start, end);
625                 // Reset position: we want to rescan last token
626                 this.index = this.tokenPreviousPosition;
627                 this.scanner.currentPosition = this.tokenPreviousPosition;
628                 this.currentTokenType = -1;
629                 return null;
630         }
631
632         /*
633          * Parse @param tag declaration
634          */
635         protected boolean parseParam() throws InvalidInputException {
636
637                 // Store current state
638                 int start = this.tagSourceStart;
639                 int end = this.tagSourceEnd;
640                 boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
641                 this.scanner.tokenizeWhiteSpace = true;
642 //              this.scanner.tokenizeLineSeparator = true;
643                 
644                 // Verify that there are whitespaces after tag
645                 int token = readToken();
646                 if (token != TerminalTokens.TokenNameWHITESPACE) {
647                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(start, this.scanner.getCurrentTokenEndPosition());
648                         this.scanner.currentPosition = start;
649                         this.index = start;
650                         this.currentTokenType = -1;
651                         this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
652                         return false;
653                 }
654                 
655                 // Get first non whitespace token
656                 this.identifierPtr = -1;
657                 this.identifierLengthPtr = -1;
658                 boolean hasMultiLines = this.scanner.currentPosition > (this.lineEnd+1);
659                 boolean isTypeParam = false;
660                 boolean valid = true, empty = true;
661                 nextToken: while (true) {
662                         this.currentTokenType = -1;
663                         try {
664                                 token = readToken();
665                         } catch (InvalidInputException e) {
666                                 valid = false;
667                         }
668                         switch (token) {
669                                 case TerminalTokens.TokenNameIdentifier :
670                                         if (valid) { 
671                                                 // store param name id
672                                                 pushIdentifier(true);
673                                                 start = this.scanner.getCurrentTokenStartPosition();
674                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
675                                                 break nextToken;
676                                         }
677                                         // fall through next case to report error
678                                 case TerminalTokens.TokenNameLESS:
679                                         if (valid && this.jdk15) {
680                                                 // store '<' in identifiers stack as we need to add it to tag element (bug 79809)
681                                                 pushIdentifier(true);
682                                                 start = this.scanner.getCurrentTokenStartPosition();
683                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
684                                                 isTypeParam = true;
685                                                 break nextToken;
686                                         }
687                                         // fall through next case to report error
688                                 default:
689                                         if (token == TerminalTokens.TokenNameLEFT_SHIFT) isTypeParam = true;
690                                         if (valid && !hasMultiLines) start = this.scanner.getCurrentTokenStartPosition();
691                                         valid = false;
692                                         if (!hasMultiLines) {
693                                                 empty = false;
694                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
695                                                 break;
696                                         }
697                                         end = this.lineEnd;
698                                         // when several lines, fall through next case to report problem immediately
699                                 case TerminalTokens.TokenNameWHITESPACE:
700                                         if (this.scanner.currentPosition > (this.lineEnd+1)) hasMultiLines = true;
701                                         if (valid) break;
702                                         // if not valid fall through next case to report error
703                                 case TerminalTokens.TokenNameEOF:
704                                         if (this.reportProblems)
705                                                 if (empty)
706                                                         this.sourceParser.problemReporter().javadocMissingParamName(start, end, this.sourceParser.modifiers);
707                                                 else if (this.jdk15 && isTypeParam)
708                                                         this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
709                                                 else
710                                                         this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
711                                         this.scanner.currentPosition = start;
712                                         this.index = start;
713                                         this.currentTokenType = -1;
714                                         this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
715                                         return false;
716                         }
717                 }
718                 
719                 // Scan more tokens for type parameter declaration
720                 if (isTypeParam && this.jdk15) {
721                         // Get type parameter name
722                         nextToken: while (true) {
723                                 this.currentTokenType = -1;
724                                 try {
725                                         token = readToken();
726                                 } catch (InvalidInputException e) {
727                                         valid = false;
728                                 }
729                                 switch (token) {
730                                         case TerminalTokens.TokenNameWHITESPACE:
731                                                 if (valid && this.scanner.currentPosition <= (this.lineEnd+1)) break;
732                                                 // if not valid fall through next case to report error
733                                         case TerminalTokens.TokenNameEOF:
734                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
735                                                 this.scanner.currentPosition = start;
736                                                 this.index = start;
737                                                 this.currentTokenType = -1;
738                                                 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
739                                                 return false;
740                                         case TerminalTokens.TokenNameIdentifier :
741                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
742                                                 if (valid) {
743                                                         // store param name id
744                                                         pushIdentifier(false);
745                                                         break nextToken;
746                                                 }
747                                                 break;
748                                         default:
749                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
750                                                 valid = false;
751                                                 break;
752                                 }
753                         }
754                         
755                         // Get last character of type parameter declaration
756                         boolean spaces = false;
757                         nextToken: while (true) {
758                                 this.currentTokenType = -1;
759                                 try {
760                                         token = readToken();
761                                 } catch (InvalidInputException e) {
762                                         valid = false;
763                                 }
764                                 switch (token) {
765                                         case TerminalTokens.TokenNameWHITESPACE:
766                                                 if (this.scanner.currentPosition > (this.lineEnd+1)) {
767                                                         // do not accept type parameter declaration on several lines
768                                                         hasMultiLines = true;
769                                                         valid = false;
770                                                 }
771                                                 spaces = true;
772                                                 if (valid) break;
773                                                 // if not valid fall through next case to report error
774                                         case TerminalTokens.TokenNameEOF:
775                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
776                                                 this.scanner.currentPosition = start;
777                                                 this.index = start;
778                                                 this.currentTokenType = -1;
779                                                 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
780                                                 return false;
781                                         case TerminalTokens.TokenNameGREATER:
782                                                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
783                                                 if (valid) {
784                                                         // store '>' in identifiers stack as we need to add it to tag element (bug 79809)
785                                                         pushIdentifier(false);
786                                                         break nextToken;
787                                                 }
788                                                 break;
789                                         default:
790                                                 if (!spaces) end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
791                                                 valid = false;
792                                                 break;
793                                 }
794                         }
795                 }
796                 
797                 // Verify that tag name is well followed by white spaces
798                 if (valid) {
799                         this.currentTokenType = -1;
800                         int restart = this.scanner.currentPosition;
801                         try {
802                                 token = readToken();
803                         } catch (InvalidInputException e) {
804                                 valid = false;
805                         }
806                         if (token == TerminalTokens.TokenNameWHITESPACE) {
807                                 this.scanner.currentPosition = restart;
808                                 this.index = restart;
809                                 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
810                                 return pushParamName(isTypeParam);
811                         }
812                 }
813                 
814                 // Report problem
815                 this.currentTokenType = -1;
816                 end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
817                 while (readToken() != TerminalTokens.TokenNameWHITESPACE) {
818                         this.currentTokenType = -1;
819                         end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition();
820                 }
821                 if (this.reportProblems)
822                         if (this.jdk15 && isTypeParam)
823                                 this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
824                         else
825                                 this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
826                 this.scanner.currentPosition = start;
827                 this.index = start;
828                 this.currentTokenType = -1;
829                 this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
830                 return false;
831         }
832
833         /*
834          * Parse a qualified name and built a type reference if the syntax is valid.
835          */
836         protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
837
838                 // Reset identifier stack if requested
839                 if (reset) {
840                         this.identifierPtr = -1;
841                         this.identifierLengthPtr = -1;
842                 }
843
844                 // Scan tokens
845                 int primitiveToken = -1;
846                 nextToken : for (int iToken = 0; ; iToken++) {
847                         int token = readToken();
848                         switch (token) {
849                                 case TerminalTokens.TokenNameIdentifier :
850                                         if (((iToken % 2) > 0)) { // identifiers must be odd tokens
851                                                 break nextToken;
852                                         }
853                                         pushIdentifier(iToken == 0);
854                                         consumeToken();
855                                         break;
856
857                                 case TerminalTokens.TokenNameDOT :
858                                         if ((iToken % 2) == 0) { // dots must be even tokens
859                                                 throw new InvalidInputException();
860                                         }
861                                         consumeToken();
862                                         break;
863
864                                 case TerminalTokens.TokenNamevoid :
865                                 case TerminalTokens.TokenNameboolean :
866                                 case TerminalTokens.TokenNamebyte :
867                                 case TerminalTokens.TokenNamechar :
868                                 case TerminalTokens.TokenNamedouble :
869                                 case TerminalTokens.TokenNamefloat :
870                                 case TerminalTokens.TokenNameint :
871                                 case TerminalTokens.TokenNamelong :
872                                 case TerminalTokens.TokenNameshort :
873                                         if (iToken > 0) {
874                                                 throw new InvalidInputException();
875                                         }
876                                         pushIdentifier(true);
877                                         primitiveToken = token;
878                                         consumeToken();
879                                         break nextToken;
880
881                                 default :
882                                         if (iToken == 0) {
883                                                 return null;
884                                         }
885                                         if ((iToken % 2) == 0) { // cannot leave on a dot
886                                                 // Reset position: we want to rescan last token
887                                                 if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
888                                                         this.index = this.tokenPreviousPosition;
889                                                         this.scanner.currentPosition = this.tokenPreviousPosition;
890                                                         this.currentTokenType = -1;
891                                                 }
892                                                 throw new InvalidInputException();
893                                         }
894                                         break nextToken;
895                         }
896                 }
897                 // Reset position: we want to rescan last token
898                 if (this.currentTokenType != -1) {
899                         this.index = this.tokenPreviousPosition;
900                         this.scanner.currentPosition = this.tokenPreviousPosition;
901                         this.currentTokenType = -1;
902                 }
903                 this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
904                 return createTypeReference(primitiveToken);
905         }
906
907         /*
908          * Parse a reference in @see tag
909          */
910         protected boolean parseReference() throws InvalidInputException {
911                 int currentPosition = this.scanner.currentPosition;
912                 try {
913                         Object typeRef = null;
914                         Object reference = null;
915                         int previousPosition = -1;
916                         int typeRefStartPosition = -1;
917                         
918                         // Get reference tokens
919                         nextToken : while (this.index < this.scanner.eofPosition) {
920                                 previousPosition = this.index;
921                                 int token = readToken();
922                                 switch (token) {
923                                         case TerminalTokens.TokenNameStringLiteral : // @see "string"
924                                                 consumeToken();
925                                                 int start = this.scanner.getCurrentTokenStartPosition();
926                                                 if (this.tagValue == TAG_VALUE_VALUE) {
927                                                         // String reference are not allowed for @value tag
928                                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getTokenEndPosition(), this.sourceParser.modifiers);
929                                                         return false;
930                                                 }
931                                                 // If typeRef != null we may raise a warning here to let user know there's an unused reference...
932                                                 // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
933                                                 if (typeRef != null) {
934                                                         start = this.tagSourceEnd+1;
935                                                         previousPosition = start;
936                                                         typeRef = null;
937                                                 }
938                                                 // verify end line
939                                                 if (verifyEndLine(previousPosition)) {
940                                                         return true;
941                                                 }
942                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
943                                                 return false;
944                                         case TerminalTokens.TokenNameLESS : // @see "<a href="URL#Value">label</a>
945                                                 consumeToken();
946                                                 start = this.scanner.getCurrentTokenStartPosition();
947                                                 if (parseHref()) {
948                                                         consumeToken();
949                                                         // If typeRef != null we may raise a warning here to let user know there's an unused reference...
950                                                         // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
951                                                         if (typeRef != null) {
952                                                                 start = this.tagSourceEnd+1;
953                                                                 previousPosition = start;
954                                                                 typeRef = null;
955                                                         }
956                                                         if (this.tagValue == TAG_VALUE_VALUE) {
957                                                                 // String reference are not allowed for @value tag
958                                                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
959                                                                 return false;
960                                                         }
961                                                         // verify end line
962                                                         if (verifyEndLine(previousPosition)) return true;
963                                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
964                                                 }
965                                                 else if (this.tagValue == TAG_VALUE_VALUE) {
966                                                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
967                                                 }
968                                                 return false;
969                                         case TerminalTokens.TokenNameERROR :
970                                                 if (this.scanner.currentCharacter == '#') { // @see ...#member
971                                                         consumeToken();
972                                                         reference = parseMember(typeRef);
973                                                         if (reference != null) {
974                                                                 return pushSeeRef(reference);
975                                                         }
976                                                         return false;
977                                                 }
978                                                 break nextToken;
979                                         case TerminalTokens.TokenNameIdentifier :
980                                                 if (typeRef == null) {
981                                                         typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
982                                                         typeRef = parseQualifiedName(true);
983                                                         break;
984                                                 }
985                                         default :
986                                                 break nextToken;
987                                 }
988                         }
989
990                         // Verify that we got a reference
991                         if (reference == null) reference = typeRef;
992                         if (reference == null) {
993                                 this.index = this.tokenPreviousPosition;
994                                 this.scanner.currentPosition = this.tokenPreviousPosition;
995                                 this.currentTokenType = -1;
996                                 if (this.tagValue == TAG_VALUE_VALUE) {
997                                         if (this.kind == DOM_PARSER) createTag();
998                                         return true;
999                                 }
1000                                 if (this.reportProblems) {
1001                                         this.sourceParser.problemReporter().javadocMissingReference(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
1002                                 }
1003                                 return false;
1004                         }
1005
1006                         // Reset position at the end of type reference
1007                         this.index = this.lastIdentifierEndPosition+1;
1008                         this.scanner.currentPosition = this.index;
1009                         this.currentTokenType = -1;
1010
1011                         // In case of @value, we have an invalid reference (only static field refs are valid for this tag)
1012                         if (this.tagValue == TAG_VALUE_VALUE) {
1013                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(typeRefStartPosition, this.lineEnd);
1014                                 return false;
1015                         }
1016
1017                         // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
1018                         // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
1019                         char ch = peekChar();
1020                         if (ch == '(') {
1021                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocMissingHashCharacter(typeRefStartPosition, this.lineEnd, String.valueOf(this.source, typeRefStartPosition, this.lineEnd-typeRefStartPosition+1));
1022                                 return false;
1023                         }
1024
1025                         // Verify that we get white space after reference
1026                         if (!verifySpaceOrEndComment()) {
1027                                 this.index = this.tokenPreviousPosition;
1028                                 this.scanner.currentPosition = this.tokenPreviousPosition;
1029                                 this.currentTokenType = -1;
1030                                 int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
1031                                 if (this.source[end]=='\n') end--;
1032                                 if (this.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
1033                                 return false;
1034                         }
1035                         
1036                         // Everything is OK, store reference
1037                         return pushSeeRef(reference);
1038                 }
1039                 catch (InvalidInputException ex) {
1040                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(currentPosition, getTokenEndPosition());
1041                 }
1042                 // Reset position to avoid missing tokens when new line was encountered
1043                 this.index = this.tokenPreviousPosition;
1044                 this.scanner.currentPosition = this.tokenPreviousPosition;
1045                 this.currentTokenType = -1;
1046                 return false;
1047         }
1048
1049         /*
1050          * Parse tag declaration
1051          */
1052         protected abstract boolean parseTag(int previousPosition) throws InvalidInputException;
1053
1054         /*
1055          * Parse @throws tag declaration
1056          */
1057         protected boolean parseThrows() {
1058                 int start = this.scanner.currentPosition;
1059                 try {
1060                         Object typeRef = parseQualifiedName(true);
1061                         if (typeRef == null) {
1062                                 if (this.reportProblems)
1063                                         this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
1064                         } else {
1065                                 return pushThrowName(typeRef);
1066                         }
1067                 } catch (InvalidInputException ex) {
1068                         if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getTokenEndPosition());
1069                 }
1070                 return false;
1071         }
1072
1073         /*
1074          * Return current character without move index position.
1075          */
1076         protected char peekChar() {
1077                 int idx = this.index;
1078                 char c = this.source[idx++];
1079                 if (c == '\\' && this.source[idx] == 'u') {
1080                         int c1, c2, c3, c4;
1081                         idx++;
1082                         while (this.source[idx] == 'u')
1083                                 idx++;
1084                         if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
1085                                         || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
1086                                         || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
1087                                 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1088                         }
1089                 }
1090                 return c;
1091         }
1092
1093         /*
1094          * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
1095          */
1096         protected void pushIdentifier(boolean newLength) {
1097
1098                 int stackLength = this.identifierStack.length;
1099                 if (++this.identifierPtr >= stackLength) {
1100                         System.arraycopy(
1101                                 this.identifierStack, 0,
1102                                 this.identifierStack = new char[stackLength + 10][], 0,
1103                                 stackLength);
1104                         System.arraycopy(
1105                                 this.identifierPositionStack, 0,
1106                                 this.identifierPositionStack = new long[stackLength + 10], 0,
1107                                 stackLength);
1108                 }
1109                 this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
1110                 this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
1111
1112                 if (newLength) {
1113                         stackLength = this.identifierLengthStack.length;
1114                         if (++this.identifierLengthPtr >= stackLength) {
1115                                 System.arraycopy(
1116                                         this.identifierLengthStack, 0,
1117                                         this.identifierLengthStack = new int[stackLength + 10], 0,
1118                                         stackLength);
1119                         }
1120                         this.identifierLengthStack[this.identifierLengthPtr] = 1;
1121                 } else {
1122                         this.identifierLengthStack[this.identifierLengthPtr]++;
1123                 }
1124         }
1125
1126         /*
1127          * Add a new obj on top of the ast stack.
1128          * If new length is required, then add also a new length in length stack.
1129          */
1130         protected void pushOnAstStack(Object node, boolean newLength) {
1131
1132                 if (node == null) {
1133                         this.astLengthStack[++this.astLengthPtr] = 0;
1134                         return;
1135                 }
1136
1137                 int stackLength = this.astStack.length;
1138                 if (++this.astPtr >= stackLength) {
1139                         System.arraycopy(
1140                                 this.astStack, 0,
1141                                 this.astStack = new Object[stackLength + AstStackIncrement], 0,
1142                                 stackLength);
1143                         this.astPtr = stackLength;
1144                 }
1145                 this.astStack[this.astPtr] = node;
1146
1147                 if (newLength) {
1148                         stackLength = this.astLengthStack.length;
1149                         if (++this.astLengthPtr >= stackLength) {
1150                                 System.arraycopy(
1151                                         this.astLengthStack, 0,
1152                                         this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
1153                                         stackLength);
1154                         }
1155                         this.astLengthStack[this.astLengthPtr] = 1;
1156                 } else {
1157                         this.astLengthStack[this.astLengthPtr]++;
1158                 }
1159         }
1160
1161         /*
1162          * Push a param name in ast node stack.
1163          */
1164         protected abstract boolean pushParamName(boolean isTypeParam);
1165
1166         /*
1167          * Push a reference statement in ast node stack.
1168          */
1169         protected abstract boolean pushSeeRef(Object statement);
1170
1171         /*
1172          * Push a text element in ast node stack
1173          */
1174         protected abstract void pushText(int start, int end);
1175
1176         /*
1177          * Push a throws type ref in ast node stack.
1178          */
1179         protected abstract boolean pushThrowName(Object typeRef);
1180
1181         /*
1182          * Read current character and move index position.
1183          * Warning: scanner position is unchanged using this method!
1184          */
1185         protected char readChar() {
1186         
1187                 char c = this.source[this.index++];
1188                 if (c == '\\' && this.source[this.index] == 'u') {
1189                         int c1, c2, c3, c4;
1190                         int pos = this.index;
1191                         this.index++;
1192                         while (this.source[this.index] == 'u')
1193                                 this.index++;
1194                         if (!(((c1 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
1195                                         || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
1196                                         || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
1197                                 c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
1198                         } else {
1199                                 // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
1200                                 this.index = pos;
1201                         }
1202                 }
1203                 return c;
1204         }
1205
1206         /*
1207          * Read token only if previous was consumed
1208          */
1209         private int readToken() throws InvalidInputException {
1210                 if (this.currentTokenType < 0) {
1211                         this.tokenPreviousPosition = this.scanner.currentPosition;
1212                         this.currentTokenType = this.scanner.getNextToken();
1213                         if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1214                                 this.lineStarted = false;
1215                                 while (this.currentTokenType == TerminalTokens.TokenNameMULTIPLY) {
1216                                         this.currentTokenType = this.scanner.getNextToken();
1217                                 }
1218                         }
1219                         this.index = this.scanner.currentPosition;
1220                         this.lineStarted = true; // after having read a token, line is obviously started...
1221                 }
1222                 return this.currentTokenType;
1223         }
1224
1225         protected int readTokenAndConsume() throws InvalidInputException {
1226                 int token = readToken();
1227                 consumeToken();
1228                 return token;
1229         }
1230         
1231         /*
1232          * Refresh start position and length of an inline tag.
1233          */
1234         protected void refreshInlineTagPosition(int previousPosition) {
1235                 // do nothing by default
1236         }
1237
1238         public String toString() {
1239                 StringBuffer buffer = new StringBuffer();
1240                 int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
1241                 int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
1242                 if (startPos == this.source.length)
1243                         return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
1244                 if (endPos > this.source.length)
1245                         return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
1246         
1247                 char front[] = new char[startPos];
1248                 System.arraycopy(this.source, 0, front, 0, startPos);
1249         
1250                 int middleLength = (endPos - 1) - startPos + 1;
1251                 char middle[];
1252                 if (middleLength > -1) {
1253                         middle = new char[middleLength];
1254                         System.arraycopy(
1255                                 this.source, 
1256                                 startPos, 
1257                                 middle, 
1258                                 0, 
1259                                 middleLength);
1260                 } else {
1261                         middle = CharOperation.NO_CHAR;
1262                 }
1263                 
1264                 char end[] = new char[this.source.length - (endPos - 1)];
1265                 System.arraycopy(
1266                         this.source, 
1267                         (endPos - 1) + 1, 
1268                         end, 
1269                         0, 
1270                         this.source.length - (endPos - 1) - 1);
1271                 
1272                 buffer.append(front);
1273                 if (this.scanner.currentPosition<this.index) {
1274                         buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
1275                 } else {
1276                         buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
1277                 }
1278                 buffer.append(middle);
1279                 if (this.scanner.currentPosition<this.index) {
1280                         buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
1281                 } else {
1282                         buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
1283                 }
1284                 buffer.append(end);
1285
1286                 return buffer.toString();
1287         }
1288
1289         /*
1290          * Update 
1291          */
1292         protected abstract void updateDocComment();
1293
1294         /*
1295          * Update line end
1296          */
1297         protected void updateLineEnd() {
1298                 while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
1299                         if (this.linePtr < this.lastLinePtr) {
1300                                 this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
1301                         } else {
1302                                 this.lineEnd = this.endComment;
1303                                 return;
1304                         }
1305                 }
1306         }
1307
1308         /*
1309          * Verify that end of the line only contains space characters or end of comment.
1310          * Note that end of comment may be preceeding by several contiguous '*' chars.
1311          */
1312         private boolean verifyEndLine(int textPosition) {
1313                 // Special case for inline tag
1314                 if (this.inlineTagStarted) {
1315                         // expecting closing brace
1316                         if (peekChar() == '}') {
1317                                 if (this.kind == DOM_PARSER) {
1318                                         createTag();
1319                                         pushText(textPosition, this.starPosition);
1320                                 }
1321                                 return true;
1322                         }
1323                         return false;
1324                 }
1325                 
1326                 int startPosition = this.index;
1327                 int previousPosition = this.index;
1328                 this.starPosition = -1;
1329                 char ch = readChar();
1330                 nextChar: while (true) {
1331                         switch (ch) {
1332                                 case '\r':
1333                                 case '\n':
1334                                         if (this.kind == DOM_PARSER) {
1335                                                 createTag();
1336                                                 pushText(textPosition, previousPosition);
1337                                         }
1338                                         this.index = previousPosition;
1339                                         return true;
1340                                 case '\u000c' : /* FORM FEED               */
1341                                 case ' ' :                      /* SPACE                   */
1342                                 case '\t' :                     /* HORIZONTAL TABULATION   */
1343                                         if (this.starPosition >= 0) break nextChar;
1344                                         break;
1345                                 case '*':
1346                                         this.starPosition = previousPosition;
1347                                         break;
1348                                 case '/':
1349                                         if (this.starPosition >= textPosition) {
1350                                                 if (this.kind == DOM_PARSER) {
1351                                                         createTag();
1352                                                         pushText(textPosition, this.starPosition);
1353                                                 }
1354                                                 return true;
1355                                         }
1356                                 default :
1357                                         // leave loop
1358                                         break nextChar;
1359                                 
1360                         }
1361                         previousPosition = this.index;
1362                         ch = readChar();
1363                 }
1364                 this.index = startPosition;
1365                 return false;
1366         }
1367
1368         /*
1369          * Verify characters after a name matches one of following conditions:
1370          *      1- first character is a white space
1371          *      2- first character is a closing brace *and* we're currently parsing an inline tag
1372          *      3- are the end of comment (several contiguous star ('*') characters may be
1373          *          found before the last slash ('/') character).
1374          */
1375         private boolean verifySpaceOrEndComment() {
1376                 int startPosition = this.index;
1377                 // Whitespace or inline tag closing brace
1378                 char ch = peekChar();
1379                 switch (ch) {
1380                         case '}':
1381                                 return this.inlineTagStarted;
1382                         default:
1383                                 if (Character.isWhitespace(ch)) {
1384                                         return true;
1385                                 }
1386                 }
1387                 // End of comment
1388                 int previousPosition = this.index;
1389                 this.starPosition = -1;
1390                 ch = readChar();
1391                 nextChar: while (this.index<this.source.length) {
1392                         switch (ch) {
1393                                 case '*':
1394                                         // valid whatever the number of star before last '/'
1395                                         this.starPosition = previousPosition;
1396                                         break;
1397                                 case '/':
1398                                         if (this.starPosition >= startPosition) { // valid only if a star was previous character
1399                                                 return true;
1400                                         }
1401                                 default :
1402                                         // invalid whatever other character, even white spaces
1403                                         this.index = startPosition;
1404                                         return false;
1405                                 
1406                         }
1407                         previousPosition = this.index;
1408                         ch = readChar();
1409                 }
1410                 this.index = startPosition;
1411                 return false;
1412         }
1413 }