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, 2022 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 static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEOF;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
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;
protected boolean snippetInlineTagStarted = false;
private int nonRegionTagCount, inlineTagCount;
final static String SINGLE_LINE_COMMENT = "//"; //$NON-NLS-1$
// Line pointers
private int linePtr, lastLinePtr;
// Identifier stack
protected int identifierPtr;
protected char[][] identifierStack;
protected int identifierLengthPtr;
protected int[] identifierLengthStack;
protected long[] identifierPositionStack;
// Ast stack
protected final static int AST_STACK_INCREMENT = 10;
protected int astPtr;
protected Object[] astStack;
protected int astLengthPtr;
protected int[] astLengthStack;
// Uses stack
protected int usesReferencesPtr = -1;
protected TypeReference[] usesReferencesStack;
// Provides stack
protected int providesReferencesPtr = -1;
protected TypeReference[] providesReferencesStack;
// Snippet search project path as src classpath for file/class support
private String projectPath;
private List srcClasspath;
protected AbstractCommentParser(Parser sourceParser) {
this.sourceParser = sourceParser;
this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/,
sourceParser != null ? this.sourceParser.options.enablePreviewFeatures : false);
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;
setSourceComplianceLevel();
}
/* (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 == '{') || lookForTagsInSnippets()) {
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);
protected abstract Object createTypeReference(int primitiveToken, boolean canBeModule);
protected abstract Object createModuleTypeReference(int primitiveToken, int moduleRefTokenCount);
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);
}
protected 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 {
return parseArguments(receiver, true);
}
/*
* Parse argument in @see tag method reference
*/
protected Object parseArguments(Object receiver, boolean checkVerifySpaceOrEndComment) 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;
boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
this.scanner.tokenizeWhiteSpace = false;
try {
// 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 (checkVerifySpaceOrEndComment && !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();
} 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 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;
}
protected boolean lookForTagsInSnippets() {
return false;
}
/*
* Parse an URL link reference in @see tag
*/
protected boolean parseHref() throws InvalidInputException {
boolean skipComments = this.scanner.skipComments;
this.scanner.skipComments = true;
boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
this.scanner.tokenizeWhiteSpace = false;
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;
this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
}
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;
}
protected Object parseMember(Object receiver) throws InvalidInputException {
return parseMember(receiver, false);
}
/*
* Parse a method reference in @see tag
*/
protected Object parseMember(Object receiver, boolean refInStringLiteral) 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);
}
boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
this.scanner.tokenizeWhiteSpace = false;
try {
// Look for next token to know whether it's a field or method reference
int previousPosition = this.index;
try {
int token = readToken();
if (token == TerminalTokens.TokenNameLPAREN) {
consumeToken();
start = this.scanner.getCurrentTokenStartPosition();
try {
return parseArguments(receiver, !refInStringLiteral);
} 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;
}
} catch (InvalidInputException e) {
if (!refInStringLiteral || (!Scanner.INVALID_CHAR_IN_STRING.equals(e.getMessage())
&& !Scanner.INVALID_CHARACTER_CONSTANT.equals(e.getMessage()))) {
throw e;
}
}
// 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 (!refInStringLiteral && !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);
} finally {
this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
}
}
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;
}
}
private boolean isTokenModule(int token, int moduleRefTokenCount) {
return ((token == TerminalTokens.TokenNameDIVIDE)
&& (moduleRefTokenCount > 0));
}
protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
return parseQualifiedName(reset, false);
}
/*
* Parse a qualified name and built a type reference if the syntax is valid.
*/
protected Object parseQualifiedName(boolean reset, boolean allowModule) 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;
int prevToken = TerminalTokens.TokenNameNotAToken;
int curToken = TerminalTokens.TokenNameNotAToken;
int moduleRefTokenCount = 0;
boolean lookForModule = false;
boolean parsingJava15Plus = this.scanner != null ? this.scanner.sourceLevel >= ClassFileConstants.JDK15 : false;
boolean stop = false;
nextToken : for (int iToken = 0; ; iToken++) {
if (iToken == 0) {
lookForModule = false;
prevToken = TerminalTokens.TokenNameNotAToken;
} else {
prevToken = curToken;
}
if (stop) {
if (parserKind != COMPLETION_PARSER) {
break;
}
}
int token = readTokenSafely();
curToken= token;
switch (token) {
case TerminalTokens.TokenNameIdentifier :
if (((iToken & 1) != 0)) { // identifiers must be odd tokens
break nextToken;
}
pushIdentifier(iToken == 0, false);
consumeToken();
if (allowModule && parsingJava15Plus && getChar() == '/') {
lookForModule = true;
}
break;
case TerminalTokens.TokenNameRestrictedIdentifierYield:
throw new InvalidInputException(); // unexpected.
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.TokenNamenon_sealed:
case TerminalTokens.TokenNamenull:
case TerminalTokens.TokenNamepackage:
case TerminalTokens.TokenNameRestrictedIdentifierpermits:
case TerminalTokens.TokenNameprivate:
case TerminalTokens.TokenNameprotected:
case TerminalTokens.TokenNamepublic:
case TerminalTokens.TokenNameRestrictedIdentifiersealed:
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$
case TerminalTokens.TokenNameDIVIDE:
if (parsingJava15Plus && lookForModule) {
if (((iToken & 1) == 0) || (moduleRefTokenCount > 0)) { // '/' must be even token
throw new InvalidInputException();
}
moduleRefTokenCount = (iToken+1) / 2;
consumeToken();
lookForModule = false;
if (!considerNextChar()) {
stop = true;
}
break;
} // else fall through
// Note: Add other cases before this case.
//$FALL-THROUGH$
default :
if (iToken == 0) {
if (this.identifierPtr>=0) {
this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
}
return null;
}
if ((iToken & 1) == 0 && !isTokenModule(prevToken, moduleRefTokenCount)) { // cannot leave on a dot
switch (parserKind) {
case COMPLETION_PARSER:
if (this.identifierPtr>=0) {
this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
}
if (moduleRefTokenCount > 0) {
return syntaxRecoverModuleQualifiedName(primitiveToken, moduleRefTokenCount);
}
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];
}
if (moduleRefTokenCount > 0) {
return createModuleTypeReference(primitiveToken, moduleRefTokenCount);
}
return createTypeReference(primitiveToken, (allowModule && parsingJava15Plus));
}
protected boolean parseReference() throws InvalidInputException {
return parseReference(false);
}
/*
* Parse a reference in @see tag
*/
protected boolean parseReference(boolean allowModule) throws InvalidInputException {
int currentPosition = this.scanner.currentPosition;
boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
this.scanner.tokenizeWhiteSpace = false;
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();
this.scanner.tokenizeWhiteSpace = true;
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());
}
finally {
// we have to make sure that this is reset to the previous value even if an exception occurs
this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
}
// 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;
}
protected boolean parseSnippet() throws InvalidInputException {
boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace;
boolean tokenizeComments = this.scanner.tokenizeComments;
this.scanner.tokenizeWhiteSpace = true;
this.scanner.tokenizeComments = true;
int previousPosition = -1;
int lastRBracePosition = -1;
int openBraces = 1;
boolean parsingJava18Plus = this.scanner != null ? this.scanner.sourceLevel >= ClassFileConstants.JDK18 : false;
boolean valid = true;
if (!parsingJava18Plus) {
throw new InvalidInputException();
}
Object snippetTag = null;
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
try {
snippetTag = createSnippetTag();
Map snippetAttributes = new HashMap();
if (!parseTillColon(snippetAttributes)) {
int token = readTokenSafely();
boolean eitherNameorClass = token == TerminalTokens.TokenNameIdentifier || token == TerminalTokens.TokenNameclass ;
if (!eitherNameorClass ) {
this.setSnippetError(snippetTag, "Missing colon"); //$NON-NLS-1$
this.setSnippetIsValid(snippetTag, false);
if(this.reportProblems)
this.sourceParser.problemReporter().javadocInvalidSnippetMissingColon(this.index, this.lineEnd);
valid = false;
} else {
final String FILE = "file"; //$NON-NLS-1$
final String CLASS = "class"; //$NON-NLS-1$
consumeToken();
valid = false;
String snippetType = this.scanner.getCurrentTokenString();
switch (snippetType) {
case FILE:
consumeToken();
int start = this.scanner.getCurrentTokenStartPosition();
token = readTokenSafely();
if (token==TerminalTokens.TokenNameEQUAL) {
consumeToken();
token = readTokenSafely();
String regionName = null;
if (token==TerminalTokens.TokenNameERROR||token==TerminalTokens.TokenNameStringLiteral){
String fileName = this.scanner.getCurrentTokenString();
int lastIndex = fileName.length() - 1;
if ((fileName.charAt(0) =='"' && fileName.charAt(lastIndex)=='"')
||(fileName.charAt(0) =='\'' && fileName.charAt(lastIndex)=='\'')) {
fileName = fileName.substring(1, lastIndex); // strip out quotes
Path filePath = getFilePathFromFileName(fileName);
try {
valid = filePath==null ? false : readFileWithRegions(start, regionName, filePath, snippetTag);
} catch (IOException e) {
valid = false;
this.setSnippetError(snippetTag, "Error in reading file"); //$NON-NLS-1$
}
}
}
}
if (snippetTag != null) {
this.setSnippetIsValid(snippetTag, valid);
}
break;
case CLASS:
consumeToken();
start = this.scanner.getCurrentTokenStartPosition();
token = readTokenSafely();
if (token==TerminalTokens.TokenNameEQUAL) {
consumeToken();
token = readTokenSafely();
String regionName = null;
if (token==TerminalTokens.TokenNameERROR||token==TerminalTokens.TokenNameStringLiteral){
String className = this.scanner.getCurrentTokenString();
int lastIndex = className.length() - 1;
if ((className.charAt(0) =='"' && className.charAt(lastIndex)=='"')
||(className.charAt(0) =='\'' && className.charAt(lastIndex)=='\'')) {
className = className.substring(1, lastIndex); // strip out quotes
if(className.contains(".")) { //$NON-NLS-1$
className = className.replace('.', '/');
}
String fileName = className+".java";//$NON-NLS-1$
Path filePath = getFilePathFromFileName(fileName);
try {
valid = filePath==null ? false : readFileWithRegions(start, regionName, filePath, snippetTag);
} catch (IOException e) {
valid = false;
this.setSnippetError(snippetTag, "Error in reading class"); //$NON-NLS-1$
}
}
}
}
if (snippetTag != null) {
this.setSnippetIsValid(snippetTag, valid);
}
break;
default:
valid = false;
}
}
} else {
if (this.index < this.scanner.eofPosition) {
int token = readTokenSafely();
if (token == TerminalTokens.TokenNameWHITESPACE) {
if (containsNewLine(this.scanner.getCurrentTokenString())) {
consumeToken();
} else {
valid = false;
if(this.reportProblems) {
//after colon new line required
this.sourceParser.problemReporter().javadocInvalidSnippetContentNewLine(this.index, this.lineEnd);
}
this.setSnippetIsValid(snippetTag, false);
this.setSnippetError(snippetTag, "Snippet content should be in a new line"); //$NON-NLS-1$
}
}
} else {
//when will this happen?? never?
valid = false;
}
}
if(hasID(snippetAttributes)) {
this.setSnippetID(snippetTag, getID(snippetAttributes));
}
int textEndPosition = this.index;
this.textStart = this.index;
int token;
while (this.index < this.scanner.eofPosition) {
this.index = this.scanner.currentPosition;
if (openBraces == 0) {
break;
}
previousPosition = this.index;
token = readTokenSafely();
if (token == TerminalTokens.TokenNameEOF) {
break;
}
switch (token) {
case TerminalTokens.TokenNameLBRACE:
openBraces++;
textEndPosition = this.index;
break;
case TerminalTokens.TokenNameRBRACE:
openBraces--;
textEndPosition = this.index;
lastRBracePosition = this.scanner.currentPosition;
if (openBraces == 0) {
if (this.lineStarted) {
if (this.textStart == -1) {
this.textStart = previousPosition;
}
if (this.textStart != -1 && this.textStart < this.index) {
String textToBeAdded= new String( this.source, this.textStart, this.index-this.textStart);
int iindex = textToBeAdded.indexOf('*');
if (iindex > -1 && textToBeAdded.substring(0, iindex+1).trim().equals("*")) { //$NON-NLS-1$
textToBeAdded = textToBeAdded.substring(iindex+1);
}
if (!textToBeAdded.isBlank()) {
pushSnippetText(this.source, this.textStart, this.index-1, false, snippetTag);
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
}
}
}
break;
case TerminalTokens.TokenNameWHITESPACE:
if (containsNewLine(this.scanner.getCurrentTokenString())) {
if (this.lineStarted) {
if (this.textStart != -1 && this.textStart < textEndPosition) {
if (isProperties(snippetAttributes)) { //single quotes
String str = new String(this.source, this.textStart,
textEndPosition - this.textStart);
if (str.length() > 0 && (str.charAt(0) == '*' || str.charAt(0) == '#' )) {
if(str.charAt(0) == '*' )
str = str.substring(1);
str = str.stripLeading().stripTrailing();
if (str.length() > 0 && str.charAt(0) == '#'
&& str.charAt(str.length() - 1) == ':') {
str = SINGLE_LINE_COMMENT + str.substring(1, str.length() - 1);
Object innerTag = parseSnippetInlineTags(str, snippetTag, this.scanner);
if (innerTag != null) {
addSnippetInnerTag(innerTag, snippetTag);
this.snippetInlineTagStarted = true;
this.lineStarted = false;
this.textStart = -1;
break;
}
}
}
}
pushSnippetText(this.source, this.textStart, textEndPosition, true, snippetTag);
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
}
this.lineStarted = false;
// Fix bug 51650
this.textStart = -1;
}
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
String tokenString = this.scanner.getCurrentTokenString();
boolean handleNow = handleCommentLineForCurrentLine(tokenString);
boolean lvalid = false;
int indexOfLastComment = -1;
int noSingleLineComm = getNumberOfSingleLineCommentInSnippetTag(tokenString.substring(2));
if (noSingleLineComm > 0)
indexOfLastComment = indexOfLastSingleComment(tokenString.substring(2),noSingleLineComm);
if (!handleNow) {
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
Object innerTag = parseSnippetInlineTags(indexOfLastComment == -1 ? tokenString : tokenString.substring(indexOfLastComment+2), snippetTag, this.scanner);
if (innerTag != null) {
lvalid = true;
}
if( lvalid && handleNow && innerTag != snippetTag) {
if ( innerTag != snippetTag )
addSnippetInnerTag(innerTag, snippetTag);
this.snippetInlineTagStarted = true;
}
textEndPosition = this.index;
int textPos = previousPosition;
if (!lvalid) {
textPos = textEndPosition;
}
if (this.lineStarted) {
if (this.textStart == -1) {
this.textStart = previousPosition;
}
if (this.textStart != -1 && this.textStart < this.index) {
pushSnippetText(this.source, this.textStart,(innerTag!=null && indexOfLastComment >=0) ? textPos+indexOfLastComment+2:textPos, lvalid, snippetTag);
if (handleNow) {
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
}
}
if (lvalid && !handleNow) {
if ( innerTag != snippetTag )
addSnippetInnerTag(innerTag, snippetTag);
this.snippetInlineTagStarted = true;
}
//valid = valid & lvalid;
break;
default:
if (!this.lineStarted || this.textStart == -1) {
this.textStart = previousPosition;
}
this.lineStarted = true;
textEndPosition = this.index;
break;
}
consumeToken();
}
}
finally {
if(!areRegionsClosed()) {
if(this.reportProblems) {
this.sourceParser.problemReporter().javadocInvalidSnippetRegionNotClosed(this.index, this.lineEnd);
}
this.setSnippetError(snippetTag, "Region not closed"); //$NON-NLS-1$
this.setSnippetIsValid(snippetTag, false);
}
// we have to make sure that this is reset to the previous value even if an exception occurs
this.scanner.tokenizeWhiteSpace = tokenWhiteSpace;
this.scanner.tokenizeComments = tokenizeComments;
}
boolean retVal = false;
if (!valid) {
retVal = false;
} else if (openBraces == 0) {
this.scanner.currentPosition = lastRBracePosition-1;
this.index = lastRBracePosition-1;
retVal = true;
}
if (retVal == false && openBraces == 0) {
this.scanner.currentPosition = lastRBracePosition - 1;
this.index = lastRBracePosition - 1;
}
if (snippetTag != null) {
this.setSnippetIsValid(snippetTag, retVal);
}
return retVal;
}
private Path getFilePathFromFileName(String fileName) {
if(this.projectPath == null)
return null;
ArrayList sourceClassPaths = (ArrayList) this.srcClasspath;
Path filePath = null;
for (String iPath : sourceClassPaths) {
filePath = FileSystems.getDefault().getPath(this.projectPath, iPath, fileName);
if(filePath.toFile().exists())
break;
}
return filePath;
}
private boolean readFileWithRegions(int start, String regionName, Path filePath, Object snippetTag) throws IOException {
boolean valid = false;
int token;
int lastIndex;
String contents = Files.readString(filePath);
int end = this.scanner.getCurrentTokenEndPosition();
consumeToken();
boolean foundRegionDef = false;
final String REGION = "region"; //$NON-NLS-1$
while (this.index 0)
indexOfLastComment = indexOfLastSingleComment(tokenString.substring(2),noSingleLineComm);
if (!handleNow) {
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
Object innerTag = parseSnippetInlineTags(indexOfLastComment == -1 ? tokenString : tokenString.substring(indexOfLastComment+2), snippetTag, snippetScanner);
if (innerTag != null) {
lvalid = true;
}
if( lvalid && handleNow && innerTag != snippetTag) {
if ( innerTag != snippetTag )
addSnippetInnerTag(innerTag, snippetTag);
this.snippetInlineTagStarted = true;
}
textEndPosition = this.index;
int textPos = previousPosition;
if (!lvalid) {
textPos = textEndPosition;
}
if (newLineStarted) {
if (textStartPosition == -1) {
textStartPosition = previousPosition;
}
if (textStartPosition != -1 && textStartPosition < indexPos) {
pushExternalSnippetText(snippetScanner.source, textStartPosition,(innerTag!=null && indexOfLastComment >=0) ? textPos+indexOfLastComment+2:textPos, true, snippetTag);
resetTextStartPos = true;
newLineStarted = false;
if (handleNow) {
this.nonRegionTagCount = 0;
this.inlineTagCount = 0;
}
}
}
if (lvalid && !handleNow) {
if ( innerTag != snippetTag )
addSnippetInnerTag(innerTag, snippetTag);
this.snippetInlineTagStarted = true;
}
//valid = valid & lvalid;
break;
default:
textEndPosition = indexPos;
break;
}
}
} catch (InvalidInputException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
private String extractExternalSnippet(String contents, String region) {
String snippetString = ""; //$NON-NLS-1$
final String START = "start"; //$NON-NLS-1$
final String END = "end"; //$NON-NLS-1$
final String REGION = "region"; //$NON-NLS-1$
final String HIGHLIGHT = "highlight"; //$NON-NLS-1$
final String REPLACE = "replace"; //$NON-NLS-1$
final String LINK = "link"; //$NON-NLS-1$
boolean insideRegion = false;
boolean regionStarted = false;
boolean containsRegionStartSnippetTags = false;
Scanner snippetScanner = new Scanner(true, true, false, this.scanner.sourceLevel, this.scanner.complianceLevel,
null, null, false, false);
snippetScanner.setSource(contents.toCharArray());
Deque stack = new ArrayDeque<>();
if (region == null) {
return contents;
}
int count = 0;
while (true) {
int tokenType = 0;
try {
tokenType = snippetScanner.getNextToken();
if (!insideRegion && regionStarted) {
break;
}
if (tokenType == TokenNameEOF)
break;
if (tokenType == TerminalTokens.TokenNameCOMMENT_LINE) {
String commentLine = snippetScanner.getCurrentTokenString();
int noSingleLineComm = getNumberOfSingleLineCommentInSnippetTag(commentLine.substring(2));
int indexOfLastComment = 0;
String commentStr = commentLine;
if (noSingleLineComm > 0) {
indexOfLastComment = indexOfLastSingleComment(commentLine.substring(2),noSingleLineComm);
commentStr = commentLine.substring(indexOfLastComment+2);
}
Scanner commentScanner = new Scanner(false, false, false, this.scanner.sourceLevel, this.scanner.complianceLevel,
null, null, false, false);
if (commentStr.startsWith("//")) { //$NON-NLS-1$
commentStr = commentStr.substring(2);
}
commentScanner.setSource(commentStr.toCharArray());
boolean atTokenStarted = false;
boolean insideValid = false;
boolean isRegion = false;
boolean getRegionValue = false;
String attribute = null;
while (true) {
int cType = commentScanner.getNextToken();
if (cType == TokenNameEOF) {
break;
}
switch (cType) {
case TerminalTokens.TokenNameAT :
atTokenStarted = true;
insideValid = false;
isRegion = false;
getRegionValue = false;
attribute = null;
break;
case TerminalTokens.TokenNameIdentifier :
if (atTokenStarted) {
String tokenStr = commentScanner.getCurrentTokenString();
insideValid = false;
isRegion = false;
getRegionValue = false;
switch (tokenStr) {
case START:
insideValid = true;
attribute = tokenStr;
break;
case HIGHLIGHT:
case REPLACE:
case LINK:
case END:
if (insideRegion) {
insideValid = true;
attribute = tokenStr;
} else {
insideValid = false;
}
break;
default:
insideValid = false;
break;
}
} else if (insideValid) {
String tokenStr = commentScanner.getCurrentTokenString();
switch (tokenStr) {
case REGION:
isRegion = true;
break;
default:
isRegion = false;
break;
}
}
atTokenStarted = false;
break;
case TerminalTokens.TokenNameEQUAL:
if (isRegion) {
getRegionValue = true;
}
break;
case TerminalTokens.TokenNameStringLiteral:
case TerminalTokens.TokenNameSingleQuoteStringLiteral:
if (getRegionValue) {
String regionStr = commentScanner.getCurrentTokenString();
regionStr = stripQuotes(regionStr);
if (START.equals(attribute) && regionStr.equals(region)) {
insideRegion = true;
regionStarted = true;
}
if (END.equals(attribute)) {
if (regionStr.equals(region)) {
insideRegion = false;
}
stack.removeFirst();
}
if (!END.equals(attribute) && insideRegion) {
stack.addFirst(attribute);
}
attribute = null;
getRegionValue = false;
}
break;
default :
atTokenStarted = false;
isRegion = false;
break;
}
}
if (END.equals(attribute) && insideRegion) {
stack.removeFirst();
if (stack.size() == 0) {
insideRegion = false;
}
}
if (insideRegion) {
if (commentStr.stripLeading().startsWith("@start") && count++ == 0) { //$NON-NLS-1$
containsRegionStartSnippetTags = true;
snippetString = snippetString + commentLine.substring(0, indexOfLastComment) + System.lineSeparator();
}
}
}
if (insideRegion && !containsRegionStartSnippetTags) {
snippetString = snippetString + snippetScanner.getCurrentTokenString();
}
if (containsRegionStartSnippetTags) {
containsRegionStartSnippetTags = false;
}
} catch (InvalidInputException e) {
e.printStackTrace();
}
}
return snippetString;
}
private boolean isProperties(Map snippetAttributes) {
if (snippetAttributes.size()==0)
return false;
for (Map.Entry entry : snippetAttributes.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.equals("lang") && value.equals("properties")) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
}
return false;
}
private boolean hasID(Map snippetAttributes) {
if (snippetAttributes.size()==0)
return false;
for (String key: snippetAttributes.keySet()) {
if(key.equals("id")) { //$NON-NLS-1$
return true;
}
}
return false;
}
private String getID(Map snippetAttributes) {
for (Map.Entry entry : snippetAttributes.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.equals("id") ) { //$NON-NLS-1$
return value;
}
}
return ""; //$NON-NLS-1$
}
private boolean parseTillColon(Map snippetAttributes) {
boolean isValid = true;
boolean colonTokenFound = false;
int token;
String key = null;
boolean lookForValue = false;
while (this.index < this.scanner.eofPosition) {
token = readTokenSafely();
switch(token) {
case TerminalTokens.TokenNameWHITESPACE :
if (containsNewLine(this.scanner.getCurrentTokenString())) {
consumeToken();
if (this.index < this.scanner.eofPosition) {
token = readTokenSafely();
if (token == TerminalTokens.TokenNameMULTIPLY) {
consumeToken();
} else {
isValid = false;
}
} else {
isValid = false;
}
} else {
consumeToken();
}
break;
case TerminalTokens.TokenNameCOLON :
consumeToken();
colonTokenFound = true;
break;
case TerminalTokens.TokenNameclass:
if(lookForValue == false) {
isValid = false;
}
break;
case TerminalTokens.TokenNameStringLiteral:
case TerminalTokens.TokenNameIdentifier: // name and equal can come for attribute
String isFile = this.scanner.getCurrentTokenString();
if(isFile.equals("file") && lookForValue == false) { //$NON-NLS-1$
isValid = false;
break;
}
consumeToken();
if (key == null)
key = this.scanner.getCurrentTokenString();
if (lookForValue && key != null) {
String value = this.scanner.getCurrentTokenString();
snippetAttributes.put(key,
token == TerminalTokens.TokenNameStringLiteral ? value.substring(1, value.length() - 1)
: value);
lookForValue = false;
key = null;
}
break;
case TerminalTokens.TokenNameEQUAL :
consumeToken();
lookForValue=true;
break;
case TerminalTokens.TokenNameERROR:
String currentTokenString = this.scanner.getCurrentTokenString();
if(currentTokenString.length()> 1 && currentTokenString.charAt(0) =='\'' && currentTokenString.charAt(currentTokenString.length()-1) =='\'') {
if (lookForValue && key != null) {
String value = this.scanner.getCurrentTokenString();
snippetAttributes.put(key, value.substring(1, value.length() - 1));
lookForValue = false;
key = null;
break;
}
}
if (this.scanner.currentCharacter == '"') {
if (!lookForValue)
isValid = false;
}
consumeToken();
break;
default :
isValid = false;
break;
}
if (colonTokenFound || !isValid) {
break;
}
}
if (colonTokenFound) {
isValid = true;
}
return isValid;
}
public int indexOfLastSingleComment(String tokenString, int last) {
int indexOfLastCom = 0;
int temp = -1;
String tempString = tokenString;
for (int i = 0; i < last; ++i) {
temp = tempString.indexOf(SINGLE_LINE_COMMENT);
if (temp == -1) {
indexOfLastCom = 0;
break;
}
tempString = tempString.substring(++temp);
indexOfLastCom += temp;
}
return --indexOfLastCom;
}
private boolean handleCommentLineForCurrentLine(String tokenString) {
boolean handle = true;
if (tokenString != null) {
String processed= tokenString.trim();
if (processed.endsWith(":")) { //$NON-NLS-1$
handle = false;
}
}
return handle;
}
protected int getNumberOfSingleLineCommentInSnippetTag(String tokenString) {
if (tokenString != null) {
String tokenStringStripped = tokenString.stripLeading();
Scanner slScanner = new JavadocScanner(true, true, false/* nls */, this.scanner.sourceLevel,
this.scanner.complianceLevel, null/* taskTags */, null/* taskPriorities */,
false/* taskCaseSensitive */, false, true, true);
slScanner.setSource(tokenStringStripped.toCharArray());
while (true) {
try {
int tokenType = slScanner.getNextToken();
if (tokenType == TokenNameEOF)
break;
switch (tokenType) {
case TerminalTokens.TokenNameCOMMENT_LINE:
return 1 + getNumberOfSingleLineCommentInSnippetTag(tokenStringStripped
.substring(2 + tokenStringStripped.indexOf(SINGLE_LINE_COMMENT)));
}
} catch (InvalidInputException e) {
// do nothing
}
}
}
return 0;
}
protected Object parseSnippetInlineTags(String tokenString, Object snippetTag, Scanner sScanner) {
int commentStart = sScanner.getCurrentTokenStartPosition();
Object inlineTag = null;
final String REPLACE = "replace"; //$NON-NLS-1$
final String HIGHLIGHT = "highlight"; //$NON-NLS-1$
final String SUBSTRING = "substring"; //$NON-NLS-1$
final String REGEX = "regex"; //$NON-NLS-1$
final String TYPE = "type"; //$NON-NLS-1$
final String REPLACEMENT = "replacement"; //$NON-NLS-1$
final String REGION = "region"; //$NON-NLS-1$
final String END = "end"; //$NON-NLS-1$
final String LINK = "link"; //$NON-NLS-1$
final String TARGET = "target"; //$NON-NLS-1$
boolean regionClosed = false;
int initialTagCount = this.nonRegionTagCount;
List