/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-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;
import org.eclipse.jdt.internal.compiler.util.Util;
/**
* Parser specialized for decoding javadoc comments
*/
public abstract class AbstractCommentParser implements JavadocTagConstants {
// Kind of comment parser
public final static int COMPIL_PARSER= 0x0001;
public final static int DOM_PARSER= 0x0002;
public final static int SELECTION_PARSER= 0x0004;
public final static int COMPLETION_PARSER= 0x0008;
public final static int SOURCE_PARSER= 0x0010;
public final static int FORMATTER_COMMENT_PARSER= 0x0020;
protected final static int PARSER_KIND= 0x00FF;
protected final static int TEXT_PARSE= 0x0100; // flag saying that text must be stored
protected final static int TEXT_VERIF= 0x0200; // flag saying that text must be verified
// Parser recovery states
protected final static int QUALIFIED_NAME_RECOVERY= 1;
protected final static int ARGUMENT_RECOVERY= 2;
protected final static int ARGUMENT_TYPE_RECOVERY= 3;
protected final static int EMPTY_ARGUMENT_RECOVERY= 4;
// Parse infos
public Scanner scanner;
public char[] source;
protected Parser sourceParser;
private int currentTokenType= -1;
// Options
public boolean checkDocComment= false;
public boolean reportProblems;
protected long complianceLevel;
protected long sourceLevel;
// Support for {@inheritDoc}
protected long[] inheritedPositions;
protected int inheritedPositionsPtr;
private final static int INHERITED_POSITIONS_ARRAY_INCREMENT= 4;
// Results
protected boolean deprecated;
protected Object returnStatement;
// Positions
protected int javadocStart, javadocEnd;
protected int javadocTextStart, javadocTextEnd= -1;
protected int firstTagPosition;
protected int index, lineEnd;
protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
protected int textStart, memberStart;
protected int tagSourceStart, tagSourceEnd;
protected int inlineTagStart;
protected int[] lineEnds;
// Flags
protected boolean lineStarted= false;
protected boolean inlineTagStarted= false;
protected boolean abort= false;
protected int kind;
protected int tagValue= NO_TAG_VALUE;
protected int lastBlockTagValue= NO_TAG_VALUE;
// 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 final static int AST_STACK_INCREMENT= 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];
this.reportProblems= sourceParser != null;
if (sourceParser != null) {
this.checkDocComment= this.sourceParser.options.docCommentSupport;
this.sourceLevel= this.sourceParser.options.sourceLevel;
this.scanner.sourceLevel= this.sourceLevel;
this.complianceLevel= this.sourceParser.options.complianceLevel;
}
}
/* (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 commentParse() {
boolean validComment= true;
try {
// Init local variables
this.astLengthPtr= -1;
this.astPtr= -1;
this.identifierPtr= -1;
this.currentTokenType= -1;
setInlineTagStarted(false);
this.inlineTagStart= -1;
this.lineStarted= false;
this.returnStatement= null;
this.inheritedPositions= null;
this.lastBlockTagValue= NO_TAG_VALUE;
this.deprecated= false;
this.lastLinePtr= getLineNumber(this.javadocEnd);
this.textStart= -1;
this.abort= false;
char previousChar= 0;
int invalidTagLineEnd= -1;
int invalidInlineTagLineEnd= -1;
boolean lineHasStar= true;
boolean verifText= (this.kind & TEXT_VERIF) != 0;
boolean isDomParser= (this.kind & DOM_PARSER) != 0;
boolean isFormatterParser= (this.kind & FORMATTER_COMMENT_PARSER) != 0;
int lastStarPosition= -1;
// Init scanner position
this.linePtr= getLineNumber(this.firstTagPosition);
int realStart= this.linePtr == 1 ? this.javadocStart : this.scanner.getLineEnd(this.linePtr - 1) + 1;
if (realStart < this.javadocStart)
realStart= this.javadocStart;
this.scanner.resetTo(realStart, this.javadocEnd);
this.index= realStart;
if (realStart == this.javadocStart) {
readChar(); // starting '/'
readChar(); // first '*'
}
int previousPosition= this.index;
char nextCharacter= 0;
if (realStart == this.javadocStart) {
nextCharacter= readChar(); // second '*'
while (peekChar() == '*') {
nextCharacter= readChar(); // read all contiguous '*'
}
this.javadocTextStart= this.index;
}
this.lineEnd= (this.linePtr == this.lastLinePtr) ? this.javadocEnd : this.scanner.getLineEnd(this.linePtr) - 1;
this.javadocTextEnd= this.javadocEnd - 2; // supposed text end, it will be refined later...
// Loop on each comment character
int textEndPosition= -1;
while (!this.abort && this.index < this.javadocEnd) {
// Store previous position and char
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();
}
// Consume rules depending on the read character
switch (nextCharacter) {
case '@':
// Start tag parsing only if we are on line beginning or at inline tag beginning
if ((!this.lineStarted || previousChar == '{')) {
if (this.inlineTagStarted) {
setInlineTagStarted(false);
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
// Cannot have @ inside inline comment
if (this.reportProblems) {
int end= previousPosition < invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
}
validComment= false;
if (this.textStart != -1 && this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
if (isDomParser || isFormatterParser) {
refreshInlineTagPosition(textEndPosition);
}
}
if (previousChar == '{') {
if (this.textStart != -1) {
if (this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
}
setInlineTagStarted(true);
invalidInlineTagLineEnd= this.lineEnd;
} else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
pushText(this.textStart, invalidTagLineEnd);
}
this.scanner.resetTo(this.index, this.javadocEnd);
this.currentTokenType= -1; // flush token cache at line begin
try {
if (!parseTag(previousPosition)) {
// 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 (isDomParser) {
createTag();
}
this.textStart= this.tagSourceEnd + 1;
invalidTagLineEnd= this.lineEnd;
textEndPosition= this.index;
}
} catch (InvalidInputException e) {
consumeToken();
}
} else {
textEndPosition= this.index;
if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
refreshReturnStatement();
} else if (isFormatterParser) {
if (this.textStart == -1)
this.textStart= previousPosition;
}
}
this.lineStarted= true;
break;
case '\r':
case '\n':
if (this.lineStarted) {
if (isFormatterParser && !ScannerHelper.isWhitespace(previousChar)) {
textEndPosition= previousPosition;
}
if (this.textStart != -1 && this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
}
this.lineStarted= false;
lineHasStar= false;
// Fix bug 51650
this.textStart= -1;
break;
case '}':
if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
refreshReturnStatement();
}
if (this.inlineTagStarted) {
if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
refreshInlineTagPosition(previousPosition);
if (!isFormatterParser)
this.textStart= this.index;
setInlineTagStarted(false);
} else {
if (!this.lineStarted) {
this.textStart= previousPosition;
}
}
this.lineStarted= true;
textEndPosition= this.index;
break;
case '{':
if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
refreshReturnStatement();
}
if (this.inlineTagStarted) {
setInlineTagStarted(false);
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
// Cannot have opening brace in inline comment
if (this.reportProblems) {
int end= previousPosition < invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
}
if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
refreshInlineTagPosition(textEndPosition);
textEndPosition= this.index;
} else if (peekChar() != '@') {
if (this.textStart == -1)
this.textStart= previousPosition;
textEndPosition= this.index;
}
if (!this.lineStarted) {
this.textStart= previousPosition;
}
this.lineStarted= true;
this.inlineTagStart= previousPosition;
break;
case '*':
// Store the star position as text start while formatting
lastStarPosition= previousPosition;
if (previousChar != '*') {
this.starPosition= previousPosition;
if (isDomParser || isFormatterParser) {
if (lineHasStar) {
this.lineStarted= true;
if (this.textStart == -1) {
this.textStart= previousPosition;
if (this.index <= this.javadocTextEnd)
textEndPosition= this.index;
}
}
if (!this.lineStarted) {
lineHasStar= true;
}
}
}
break;
case '\u000c': /* FORM FEED */
case ' ': /* SPACE */
case '\t': /* HORIZONTAL TABULATION */
// Do not include trailing spaces in text while formatting
if (isFormatterParser) {
if (!ScannerHelper.isWhitespace(previousChar)) {
textEndPosition= previousPosition;
}
} else if (this.lineStarted && isDomParser) {
textEndPosition= this.index;
}
break;
case '/':
if (previousChar == '*') {
// End of javadoc
break;
}
// $FALL-THROUGH$ - fall through default case
default:
if (isFormatterParser && nextCharacter == '<') {
// html tags are meaningful for formatter parser
int initialIndex= this.index;
this.scanner.resetTo(this.index, this.javadocEnd);
if (!ScannerHelper.isWhitespace(previousChar)) {
textEndPosition= previousPosition;
}
if (parseHtmlTag(previousPosition, textEndPosition)) {
break;
}
if (this.abort)
return false;
// Wrong html syntax continue to process character normally
this.scanner.currentPosition= initialIndex;
this.index= initialIndex;
}
if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
refreshReturnStatement();
}
if (!this.lineStarted || this.textStart == -1) {
this.textStart= previousPosition;
}
this.lineStarted= true;
textEndPosition= this.index;
break;
}
}
this.javadocTextEnd= this.starPosition - 1;
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
// Cannot leave comment inside inline comment
if (this.inlineTagStarted) {
if (this.reportProblems) {
int end= this.javadocTextEnd < invalidInlineTagLineEnd ? this.javadocTextEnd : invalidInlineTagLineEnd;
if (this.index >= this.javadocEnd)
end= invalidInlineTagLineEnd;
this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
}
if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) {
pushText(this.textStart, textEndPosition);
}
refreshInlineTagPosition(textEndPosition);
setInlineTagStarted(false);
} else if (this.lineStarted && this.textStart != -1 && this.textStart <= textEndPosition && (this.textStart < this.starPosition || this.starPosition == lastStarPosition)) {
pushText(this.textStart, textEndPosition);
}
updateDocComment();
} catch (Exception ex) {
validComment= false;
}
return validComment;
}
protected void consumeToken() {
this.currentTokenType= -1; // flush token cache
updateLineEnd();
}
protected abstract Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
protected boolean createFakeReference(int start) {
// Do nothing by default
return true;
}
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 void createTag();
protected abstract Object createTypeReference(int primitiveToken);
private int getIndexPosition() {
if (this.index > this.lineEnd) {
return this.lineEnd;
} else {
return this.index - 1;
}
}
/**
* 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 Util.getLineNumber(position, this.scanner.lineEnds, 0, this.scanner.linePtr);
}
if (this.lineEnds == null)
return 1;
return Util.getLineNumber(position, this.lineEnds, 0, this.lineEnds.length - 1);
}
private int getTokenEndPosition() {
if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
return this.lineEnd;
} else {
return this.scanner.getCurrentTokenEndPosition();
}
}
/**
* @return Returns the currentTokenType.
*/
protected int getCurrentTokenType() {
return this.currentTokenType;
}
/*
* Parse argument in @see tag method reference
*/
protected 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();
Object typeRef= null;
int dim= 0;
boolean isVarargs= false;
long[] dimPositions= new long[20]; // assume that there won't be more than 20 dimensions...
char[] name= null;
long argNamePos= -1;
// Parse arguments declaration if method reference
nextArg: while (this.index < this.scanner.eofPosition) {
// Read argument type reference
try {
typeRef= parseQualifiedName(false);
if (this.abort)
return null; // May be aborted by specialized parser
} 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.reportProblems)
this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
return null;
}
this.lineStarted= true;
return createMethodReference(receiver, null);
}
break nextArg;
}
iToken++;
// Read possible additional type info
dim= 0;
isVarargs= false;
if (readToken() == TerminalTokens.TokenNameLBRACKET) {
// array declaration
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();
}
} else if (readToken() == TerminalTokens.TokenNameELLIPSIS) {
// ellipsis declaration
int dimStart= this.scanner.getCurrentTokenStartPosition();
dimPositions[dim++]= (((long)dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
consumeToken();
isVarargs= true;
}
// Read argument name
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();
name= argName == null ? CharOperation.NO_CHAR : argName;
if (token == TerminalTokens.TokenNameCOMMA) {
// Create new argument
Object argument= createArgumentReference(name, dim, isVarargs, typeRef, dimPositions, argNamePos);
if (this.abort)
return null; // May be aborted by specialized parser
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.reportProblems)
this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
return null;
}
// Create new argument
Object argument= createArgumentReference(name, dim, isVarargs, typeRef, dimPositions, argNamePos);
if (this.abort)
return null; // May be aborted by specialized parser
arguments.add(argument);
consumeToken();
return createMethodReference(receiver, arguments);
} else {
break nextArg;
}
}
// Something wrong happened => Invalid input
throw new InvalidInputException();
}
/**
* Parse a possible HTML tag like:
* <ul>
* <li><code>
* <li><br>
* <li><h?>
* </ul>
*
* Note that the default is to do nothing!
*
* @param previousPosition The position of the '<' character on which the tag might start
* @param endTextPosition The position of the end of the previous text
* @return <code>true</code> if a valid html tag has been parsed, <code>false</code>
* otherwise
* @throws InvalidInputException If any problem happens during the parse in this area
*/
protected boolean parseHtmlTag(int previousPosition, int endTextPosition) throws InvalidInputException {
return false;
}
/*
* Parse an URL link reference in @see tag
*/
protected boolean parseHref() throws InvalidInputException {
boolean skipComments= this.scanner.skipComments;
this.scanner.skipComments= true;
try {
int start= this.scanner.getCurrentTokenStartPosition();
char currentChar= readChar();
if (currentChar == 'a' || currentChar == 'A') {
this.scanner.currentPosition= this.index;
if (readToken() == TerminalTokens.TokenNameIdentifier) {
consumeToken();
try {
if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), HREF_TAG, false) &&
readToken() == TerminalTokens.TokenNameEQUAL) {
consumeToken();
if (readToken() == TerminalTokens.TokenNameStringLiteral) {
consumeToken();
while (this.index < this.javadocEnd) { // main loop to search for the </a> pattern
// Skip all characters after string literal until closing '>' (see bug 68726)
while (readToken() != TerminalTokens.TokenNameGREATER) {
if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' ||
(this.inlineTagStarted && 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd);
}
return false;
}
this.currentTokenType= -1; // consume token without updating line end
}
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 == '@' ||
(this.inlineTagStarted && 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd);
}
return false;
}
consumeToken();
}
consumeToken();
start= this.scanner.getCurrentTokenStartPosition();
currentChar= readChar();
// search for the </a> pattern and store last char read
if (currentChar == '/') {
currentChar= readChar();
if (currentChar == 'a' || currentChar == 'A') {
currentChar= readChar();
if (currentChar == '>') {
return true; // valid href
}
}
}
// search for invalid char in tags
if (currentChar == '\r' || currentChar == '\n' || currentChar == '\t' || currentChar == ' ') {
break;
}
}
}
}
} 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after...
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd);
}
} finally {
this.scanner.skipComments= skipComments;
}
return false;
}
/*
* Parse tag followed by an identifier
*/
protected boolean parseIdentifierTag(boolean report) {
int token= readTokenSafely();
switch (token) {
case TerminalTokens.TokenNameIdentifier:
pushIdentifier(true, false);
return true;
}
if (report) {
this.sourceParser.problemReporter().javadocMissingIdentifier(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
}
return false;
}
/*
* Parse a method reference in @see tag
*/
protected 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) {
if (this.scanner.currentCharacter == '.') { // member name may be qualified (inner class constructor reference)
parseQualifiedName(true);
} else {
consumeToken();
pushIdentifier(true, false);
}
// 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.reportProblems)
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.reportProblems)
this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
return null;
}
return createFieldReference(receiver);
}
int end= getTokenEndPosition() - 1;
end= start > end ? start : end;
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidReference(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() throws InvalidInputException {
// Store current state
int start= this.tagSourceStart;
int end= this.tagSourceEnd;
boolean tokenWhiteSpace= this.scanner.tokenizeWhiteSpace;
this.scanner.tokenizeWhiteSpace= true;
try {
// Verify that there are whitespaces after tag
boolean isCompletionParser= (this.kind & COMPLETION_PARSER) != 0;
if (this.scanner.currentCharacter != ' ' && !ScannerHelper.isWhitespace(this.scanner.currentCharacter)) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidTag(start, this.scanner.getCurrentTokenEndPosition());
if (!isCompletionParser) {
this.scanner.currentPosition= start;
this.index= start;
}
this.currentTokenType= -1;
return false;
}
// Get first non whitespace token
this.identifierPtr= -1;
this.identifierLengthPtr= -1;
boolean hasMultiLines= this.scanner.currentPosition > (this.lineEnd + 1);
boolean isTypeParam= false;
boolean valid= true, empty= true;
boolean mayBeGeneric= this.sourceLevel >= ClassFileConstants.JDK1_5;
int token= -1;
nextToken: while (true) {
this.currentTokenType= -1;
try {
token= readToken();
} catch (InvalidInputException e) {
valid= false;
}
switch (token) {
case TerminalTokens.TokenNameIdentifier:
if (valid) {
// store param name id
pushIdentifier(true, false);
start= this.scanner.getCurrentTokenStartPosition();
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
break nextToken;
}
// $FALL-THROUGH$ - fall through next case to report error
case TerminalTokens.TokenNameLESS:
if (valid && mayBeGeneric) {
// store '<' in identifiers stack as we need to add it to tag element (bug 79809)
pushIdentifier(true, true);
start= this.scanner.getCurrentTokenStartPosition();
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
isTypeParam= true;
break nextToken;
}
// $FALL-THROUGH$ - fall through next case to report error
default:
if (token == TerminalTokens.TokenNameLEFT_SHIFT)
isTypeParam= true;
if (valid && !hasMultiLines)
start= this.scanner.getCurrentTokenStartPosition();
valid= false;
if (!hasMultiLines) {
empty= false;
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
break;
}
end= this.lineEnd;
// $FALL-THROUGH$ - when several lines, fall through next case to report problem immediately
case TerminalTokens.TokenNameWHITESPACE:
if (this.scanner.currentPosition > (this.lineEnd + 1))
hasMultiLines= true;
if (valid)
break;
// $FALL-THROUGH$ - if not valid fall through next case to report error
case TerminalTokens.TokenNameEOF:
if (this.reportProblems)
if (empty)
this.sourceParser.problemReporter().javadocMissingParamName(start, end, this.sourceParser.modifiers);
else if (mayBeGeneric && isTypeParam)
this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
else
this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
if (!isCompletionParser) {
this.scanner.currentPosition= start;
this.index= start;
}
this.currentTokenType= -1;
return false;
}
}
// Scan more tokens for type parameter declaration
if (isTypeParam && mayBeGeneric) {
// Get type parameter name
nextToken: while (true) {
this.currentTokenType= -1;
try {
token= readToken();
} catch (InvalidInputException e) {
valid= false;
}
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (valid && this.scanner.currentPosition <= (this.lineEnd + 1)) {
break;
}
// $FALL-THROUGH$ - if not valid fall through next case to report error
case TerminalTokens.TokenNameEOF:
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
if (!isCompletionParser) {
this.scanner.currentPosition= start;
this.index= start;
}
this.currentTokenType= -1;
return false;
case TerminalTokens.TokenNameIdentifier:
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
if (valid) {
// store param name id
pushIdentifier(false, false);
break nextToken;
}
break;
default:
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
valid= false;
break;
}
}
// Get last character of type parameter declaration
boolean spaces= false;
nextToken: while (true) {
this.currentTokenType= -1;
try {
token= readToken();
} catch (InvalidInputException e) {
valid= false;
}
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (this.scanner.currentPosition > (this.lineEnd + 1)) {
// do not accept type parameter declaration on several lines
hasMultiLines= true;
valid= false;
}
spaces= true;
if (valid)
break;
// $FALL-THROUGH$ - if not valid fall through next case to report error
case TerminalTokens.TokenNameEOF:
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
if (!isCompletionParser) {
this.scanner.currentPosition= start;
this.index= start;
}
this.currentTokenType= -1;
return false;
case TerminalTokens.TokenNameGREATER:
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
if (valid) {
// store '>' in identifiers stack as we need to add it to tag element (bug 79809)
pushIdentifier(false, true);
break nextToken;
}
break;
default:
if (!spaces)
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
valid= false;
break;
}
}
}
// Verify that tag name is well followed by white spaces
if (valid) {
this.currentTokenType= -1;
int restart= this.scanner.currentPosition;
try {
token= readTokenAndConsume();
} catch (InvalidInputException e) {
valid= false;
}
if (token == TerminalTokens.TokenNameWHITESPACE) {
this.scanner.resetTo(restart, this.javadocEnd);
this.index= restart;
return pushParamName(isTypeParam);
}
}
// Report problem
this.currentTokenType= -1;
if (isCompletionParser)
return false;
if (this.reportProblems) {
// we only need end if we report problems
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
try {
while ((token= readToken()) != TerminalTokens.TokenNameWHITESPACE && token != TerminalTokens.TokenNameEOF) {
this.currentTokenType= -1;
end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition();
}
} catch (InvalidInputException e) {
end= this.lineEnd;
}
if (mayBeGeneric && isTypeParam)
this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end);
else
this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end);
}
this.scanner.currentPosition= start;
this.index= start;
this.currentTokenType= -1;
return false;
} finally {
// we have to make sure that this is reset to the previous value even if an exception occurs
this.scanner.tokenizeWhiteSpace= tokenWhiteSpace;
}
}
/*
* 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;
int parserKind= this.kind & PARSER_KIND;
nextToken: for (int iToken= 0;; iToken++) {
int token= readTokenSafely();
switch (token) {
case TerminalTokens.TokenNameIdentifier:
if (((iToken & 1) != 0)) { // identifiers must be odd tokens
break nextToken;
}
pushIdentifier(iToken == 0, false);
consumeToken();
break;
case TerminalTokens.TokenNameDOT:
if ((iToken & 1) == 0) { // dots must be even tokens
throw new InvalidInputException();
}
consumeToken();
break;
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:
if (iToken == 0) {
pushIdentifier(true, true);
primitiveToken= token;
consumeToken();
break nextToken;
}
// Fall through default case to verify that we do not leave on a dot
//$FALL-THROUGH$
default:
if (iToken == 0) {
if (this.identifierPtr >= 0) {
this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr];
}
return null;
}
if ((iToken & 1) == 0) { // cannot leave on a dot
switch (parserKind) {
case COMPLETION_PARSER:
if (this.identifierPtr >= 0) {
this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr];
}
return syntaxRecoverQualifiedName(primitiveToken);
case DOM_PARSER:
if (this.currentTokenType != -1) {
// Reset position: we want to rescan last token
this.index= this.tokenPreviousPosition;
this.scanner.currentPosition= this.tokenPreviousPosition;
this.currentTokenType= -1;
}
// $FALL-THROUGH$ - fall through default case to raise exception
default:
throw new InvalidInputException();
}
}
break nextToken;
}
}
// Reset position: we want to rescan last token
if (parserKind != COMPLETION_PARSER && this.currentTokenType != -1) {
this.index= this.tokenPreviousPosition;
this.scanner.currentPosition= this.tokenPreviousPosition;
this.currentTokenType= -1;
}
if (this.identifierPtr >= 0) {
this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr];
}
return createTypeReference(primitiveToken);
}
/*
* Parse a reference in @see tag
*/
protected boolean parseReference() throws InvalidInputException {
int currentPosition= this.scanner.currentPosition;
try {
Object typeRef= null;
Object reference= null;
int previousPosition= -1;
int typeRefStartPosition= -1;
// Get reference tokens
nextToken: while (this.index < this.scanner.eofPosition) {
previousPosition= this.index;
int token= readTokenSafely();
switch (token) {
case TerminalTokens.TokenNameStringLiteral: // @see "string"
// 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)
break nextToken;
consumeToken();
int start= this.scanner.getCurrentTokenStartPosition();
if (this.tagValue == TAG_VALUE_VALUE) {
// String reference are not allowed for @value tag
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidValueReference(start, getTokenEndPosition(), this.sourceParser.modifiers);
return false;
}
// verify end line
if (verifyEndLine(previousPosition)) {
return createFakeReference(start);
}
if (this.reportProblems)
this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
return false;
case TerminalTokens.TokenNameLESS: // @see <a href="URL#Value">label</a>
// 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)
break nextToken;
consumeToken();
start= this.scanner.getCurrentTokenStartPosition();
if (parseHref()) {
consumeToken();
if (this.tagValue == TAG_VALUE_VALUE) {
// String reference are not allowed for @value tag
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
return false;
}
// verify end line
if (verifyEndLine(previousPosition)) {
return createFakeReference(start);
}
if (this.reportProblems)
this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd);
} else if (this.tagValue == TAG_VALUE_VALUE) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers);
}
return false;
case TerminalTokens.TokenNameERROR:
consumeToken();
if (this.scanner.currentCharacter == '#') { // @see ...#member
reference= parseMember(typeRef);
if (reference != null) {
return pushSeeRef(reference);
}
return false;
}
char[] currentError= this.scanner.getCurrentIdentifierSource();
if (currentError.length > 0 && currentError[0] == '"') {
if (this.reportProblems) {
boolean isUrlRef= false;
if (this.tagValue == TAG_SEE_VALUE) {
int length= currentError.length, i= 1 /* first char is " */;
while (i < length && ScannerHelper.isLetter(currentError[i])) {
i++;
}
if (i < (length - 2) && currentError[i] == ':' && currentError[i + 1] == '/' && currentError[i + 2] == '/') {
isUrlRef= true;
}
}
if (isUrlRef) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=207765
// handle invalid URL references in javadoc with dedicated message
this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(this.scanner.getCurrentTokenStartPosition(), getTokenEndPosition());
} else {
this.sourceParser.problemReporter().javadocInvalidReference(this.scanner.getCurrentTokenStartPosition(), getTokenEndPosition());
}
}
return false;
}
break nextToken;
case TerminalTokens.TokenNameIdentifier:
if (typeRef == null) {
typeRefStartPosition= this.scanner.getCurrentTokenStartPosition();
typeRef= parseQualifiedName(true);
if (this.abort)
return false; // May be aborted by specialized parser
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.tagValue == TAG_VALUE_VALUE) {
if ((this.kind & DOM_PARSER) != 0)
createTag();
return true;
}
if (this.reportProblems) {
this.sourceParser.problemReporter().javadocMissingReference(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
}
return false;
}
// Reset position at the end of type reference
if (this.lastIdentifierEndPosition > this.javadocStart) {
this.index= this.lastIdentifierEndPosition + 1;
this.scanner.currentPosition= this.index;
}
this.currentTokenType= -1;
// In case of @value, we have an invalid reference (only static field refs are valid for this tag)
if (this.tagValue == TAG_VALUE_VALUE) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidReference(typeRefStartPosition, this.lineEnd);
return false;
}
int currentIndex= this.index; // store current index
char ch= readChar();
switch (ch) {
// 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
case '(':
if (this.reportProblems)
this.sourceParser.problemReporter().javadocMissingHashCharacter(typeRefStartPosition, this.lineEnd,
String.valueOf(this.source, typeRefStartPosition, this.lineEnd - typeRefStartPosition + 1));
return false;
// Search for the :// URL pattern
// See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=168849
case ':':
ch= readChar();
if (ch == '/' && ch == readChar()) {
if (this.reportProblems) {
this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(typeRefStartPosition, this.lineEnd);
return false;
}
}
}
// revert to last stored index
this.index= currentIndex;
// 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.reportProblems)
this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
return false;
}
// Everything is OK, store reference
return pushSeeRef(reference);
} catch (InvalidInputException ex) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidReference(currentPosition, getTokenEndPosition());
}
// 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 tag declaration
*/
protected abstract boolean parseTag(int previousPosition) throws InvalidInputException;
/*
* Parse @throws tag declaration
*/
protected boolean parseThrows() {
int start= this.scanner.currentPosition;
try {
Object typeRef= parseQualifiedName(true);
if (this.abort)
return false; // May be aborted by specialized parser
if (typeRef == null) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
} else {
return pushThrowName(typeRef);
}
} catch (InvalidInputException ex) {
if (this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getTokenEndPosition());
}
return false;
}
/*
* Return current character without move index position.
*/
protected 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= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
|| ((c2= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
|| ((c3= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4= ScannerHelper.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, boolean isToken) {
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]= isToken ? this.scanner.getCurrentTokenSource() : 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) {
int stackLength= this.astLengthStack.length;
if (++this.astLengthPtr >= stackLength) {
System.arraycopy(
this.astLengthStack, 0,
this.astLengthStack= new int[stackLength + AST_STACK_INCREMENT], 0,
stackLength);
}
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 + AST_STACK_INCREMENT], 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 + AST_STACK_INCREMENT], 0,
stackLength);
}
this.astLengthStack[this.astLengthPtr]= 1;
} else {
this.astLengthStack[this.astLengthPtr]++;
}
}
/*
* Push a param name in ast node stack.
*/
protected abstract boolean pushParamName(boolean isTypeParam);
/*
* Push a reference statement in ast node stack.
*/
protected abstract boolean pushSeeRef(Object statement);
/*
* Push a text element in ast node stack
*/
protected void pushText(int start, int end) {
// do not store text by default
}
/*
* Push a throws type ref in ast node stack.
*/
protected abstract boolean pushThrowName(Object typeRef);
/*
* 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= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
|| ((c2= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
|| ((c3= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4= ScannerHelper.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
*/
protected 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;
}
protected int readTokenAndConsume() throws InvalidInputException {
int token= readToken();
consumeToken();
return token;
}
/*
* Read token without throwing any InvalidInputException exception.
* Returns TerminalTokens.TokenNameERROR instead.
*/
protected int readTokenSafely() {
int token= TerminalTokens.TokenNameERROR;
try {
token= readToken();
} catch (InvalidInputException iie) {
// token is already set to error
}
return token;
}
protected void recordInheritedPosition(long position) {
if (this.inheritedPositions == null) {
this.inheritedPositions= new long[INHERITED_POSITIONS_ARRAY_INCREMENT];
this.inheritedPositionsPtr= 0;
} else {
if (this.inheritedPositionsPtr == this.inheritedPositions.length) {
System.arraycopy(
this.inheritedPositions, 0,
this.inheritedPositions= new long[this.inheritedPositionsPtr + INHERITED_POSITIONS_ARRAY_INCREMENT], 0,
this.inheritedPositionsPtr);
}
}
this.inheritedPositions[this.inheritedPositionsPtr++]= position;
}
/*
* Refresh start position and length of an inline tag.
*/
protected void refreshInlineTagPosition(int previousPosition) {
// do nothing by default
}
/*
* Refresh return statement
*/
protected void refreshReturnStatement() {
// do nothing by default
}
/**
* @param started the inlineTagStarted to set
*/
protected void setInlineTagStarted(boolean started) {
this.inlineTagStarted= started;
}
/*
* Entry point for recovery on invalid syntax
*/
protected Object syntaxRecoverQualifiedName(int primitiveToken) throws InvalidInputException {
// do nothing, just an entry point for recovery
return null;
}
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.javadocEnd;
return;
}
}
}
/*
* Verify that end of the line only contains space characters or end of comment.
* Note that end of comment may be preceding by several contiguous '*' chars.
*/
protected boolean verifyEndLine(int textPosition) {
boolean domParser= (this.kind & DOM_PARSER) != 0;
// Special case for inline tag
if (this.inlineTagStarted) {
// expecting closing brace
if (peekChar() == '}') {
if (domParser) {
createTag();
pushText(textPosition, this.starPosition);
}
return true;
}
return false;
}
int startPosition= this.index;
int previousPosition= this.index;
this.starPosition= -1;
char ch= readChar();
nextChar: while (true) {
switch (ch) {
case '\r':
case '\n':
if (domParser) {
createTag();
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) { // valid only if a star was the previous character
if (domParser) {
createTag();
pushText(textPosition, this.starPosition);
}
return true;
}
break nextChar;
default:
// leave loop
break nextChar;
}
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).
*/
protected boolean verifySpaceOrEndComment() {
this.starPosition= -1;
int startPosition= this.index;
// Whitespace or inline tag closing brace
char ch= peekChar();
switch (ch) {
case '}':
return this.inlineTagStarted;
default:
if (ScannerHelper.isWhitespace(ch)) {
return true;
}
}
// End of comment
int previousPosition= this.index;
ch= readChar();
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 the previous character
return true;
}
// $FALL-THROUGH$ - fall through to invalid case
default:
// invalid whatever other character, even white spaces
this.index= startPosition;
return false;
}
previousPosition= this.index;
ch= readChar();
}
this.index= startPosition;
return false;
}
}