org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
Eclipse Compiler for Java(TM)
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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
*/
@SuppressWarnings({"rawtypes", "unchecked"})
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 setJavadocPositions = 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...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345
// when parsing tags such as @code and @literal,
// any tag should be discarded and considered as plain text until
// properly closed with closing brace
boolean considerTagAsPlainText = false;
// internal counter for opening braces
int openingBraces = 0;
// 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
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: ignore all tags when inside @literal or @code tags
if (considerTagAsPlainText) {
// new tag found
if (!this.lineStarted) {
// we may want to report invalid syntax when no closing brace found,
// or when incoherent number of closing braces found
if (openingBraces > 0 && this.reportProblems) {
this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, invalidInlineTagLineEnd);
}
considerTagAsPlainText = false;
this.inlineTagStarted = false;
openingBraces = 0;
}
} else 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= 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
while (readToken() == TerminalTokens.TokenNameLBRACKET) {
int dimStart = this.scanner.getCurrentTokenStartPosition();
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:
*
* - <code>
*
- <br>
*
- <h?>
*
*
* 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 true
if a valid html tag has been parsed, false
* 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 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 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 label
// 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 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.getHexadecimalValue(this.source[idx++])) > 15 || c1 < 0)
|| ((c2 = ScannerHelper.getHexadecimalValue(this.source[idx++])) > 15 || c2 < 0)
|| ((c3 = ScannerHelper.getHexadecimalValue(this.source[idx++])) > 15 || c3 < 0)
|| ((c4 = ScannerHelper.getHexadecimalValue(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.getHexadecimalValue(this.source[this.index++])) > 15 || c1 < 0)
|| ((c2 = ScannerHelper.getHexadecimalValue(this.source[this.index++])) > 15 || c2 < 0)
|| ((c3 = ScannerHelper.getHexadecimalValue(this.source[this.index++])) > 15 || c3 < 0)
|| ((c4 = ScannerHelper.getHexadecimalValue(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;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
int startPos = this.scanner.currentPosition 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"); //$NON-NLS-1$
} else {
buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
}
buffer.append(middle);
if (this.scanner.currentPosition (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.index);
}
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= 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;
}
}