blob: cfe0d5fcfdd747c7b5cb34fe48985c00e4a6d108 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 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.core.dom;
import java.util.ArrayList;
import java.util.Iterator;
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.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
/**
* Internal parser used for decoding doc comments.
*
* @since 3.0
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
class DocCommentParser extends AbstractCommentParser {
private Javadoc docComment;
private AST ast;
DocCommentParser(AST ast, Scanner scanner, boolean check) {
super(null);
this.ast = ast;
this.scanner = scanner;
switch(this.ast.apiLevel()) {
case AST.JLS2_INTERNAL :
this.sourceLevel = ClassFileConstants.JDK1_3;
break;
case AST.JLS3_INTERNAL:
this.sourceLevel = ClassFileConstants.JDK1_5;
break;
default:
// AST.JLS4 for now
this.sourceLevel = ClassFileConstants.JDK1_7;
}
this.checkDocComment = check;
this.kind = DOM_PARSER | TEXT_PARSE;
}
/* (non-Javadoc)
* Returns true if tag @deprecated is present in annotation.
*
* If annotation checking is enabled, will also construct an Annotation node, which will be stored into Parser.annotation
* slot for being consumed later on.
*/
public Javadoc parse(int[] positions) {
return parse(positions[0], positions[1]-positions[0]);
}
public Javadoc parse(int start, int length) {
// Init
this.source = this.scanner.source;
this.lineEnds = this.scanner.lineEnds;
this.docComment = new Javadoc(this.ast);
// Parse
if (this.checkDocComment) {
this.javadocStart = start;
this.javadocEnd = start+length-1;
this.firstTagPosition = this.javadocStart;
commentParse();
}
this.docComment.setSourceRange(start, length);
if (this.ast.apiLevel == AST.JLS2_INTERNAL) {
setComment(start, length); // backward compatibility
}
return this.docComment;
}
/**
* Sets the comment starting at the given position and with the given length.
* <p>
* Note the only purpose of this method is to hide deprecated warnings.
* @deprecated mark deprecated to hide deprecated usage
*/
private void setComment(int start, int length) {
this.docComment.setComment(new String(this.source, start, length));
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("javadoc: ").append(this.docComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
buffer.append(super.toString());
return buffer.toString();
}
@Override
protected Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object typeRef, long[] dimPositions, long argNamePos) throws InvalidInputException {
try {
MethodRefParameter argument = this.ast.newMethodRefParameter();
ASTNode node = (ASTNode) typeRef;
int argStart = node.getStartPosition();
int argEnd = node.getStartPosition()+node.getLength()-1;
if (dim > 0) argEnd = (int) dimPositions[dim-1];
if (argNamePos >= 0) argEnd = (int) argNamePos;
if (name.length != 0) {
final SimpleName argName = new SimpleName(this.ast);
argName.internalSetIdentifier(new String(name));
argument.setName(argName);
int argNameStart = (int) (argNamePos >>> 32);
argName.setSourceRange(argNameStart, argEnd-argNameStart+1);
}
Type argType = null;
if (node.getNodeType() == ASTNode.PRIMITIVE_TYPE) {
argType = (PrimitiveType) node;
} else {
Name argTypeName = (Name) node;
argType = this.ast.newSimpleType(argTypeName);
argType.setSourceRange(argStart, node.getLength());
}
if (dim > 0 && !isVarargs) {
if (this.ast.apiLevel <= AST.JLS4_INTERNAL) {
for (int i=0; i<dim; i++) {
argType = this.ast.newArrayType(argType);
argType.setSourceRange(argStart, ((int) dimPositions[i])-argStart+1);
}
} else {
ArrayType argArrayType = this.ast.newArrayType(argType, 0);
argType = argArrayType;
argType.setSourceRange(argStart, ((int) dimPositions[dim-1])-argStart+1);
for (int i=0; i<dim; i++) {
Dimension dimension = this.ast.newDimension();
int dimStart = (int) (dimPositions[i] >>> 32);
int dimEnd = (int) dimPositions[i];
dimension.setSourceRange(dimStart, dimEnd-dimStart+1);
argArrayType.dimensions().add(dimension);
}
}
}
argument.setType(argType);
if (this.ast.apiLevel > AST.JLS8_INTERNAL) {
argument.setVarargs(isVarargs);
}
argument.setSourceRange(argStart, argEnd - argStart + 1);
return argument;
}
catch (ClassCastException ex) {
throw new InvalidInputException();
}
}
@Override
protected Object createFieldReference(Object receiver) throws InvalidInputException {
try {
MemberRef fieldRef = this.ast.newMemberRef();
SimpleName fieldName = new SimpleName(this.ast);
fieldName.internalSetIdentifier(new String(this.identifierStack[0]));
fieldRef.setName(fieldName);
int start = (int) (this.identifierPositionStack[0] >>> 32);
int end = (int) this.identifierPositionStack[0];
fieldName.setSourceRange(start, end - start + 1);
if (receiver == null) {
start = this.memberStart;
fieldRef.setSourceRange(start, end - start + 1);
} else {
Name typeRef = (Name) receiver;
fieldRef.setQualifier(typeRef);
start = typeRef.getStartPosition();
end = fieldName.getStartPosition()+fieldName.getLength()-1;
fieldRef.setSourceRange(start, end-start+1);
}
return fieldRef;
}
catch (ClassCastException ex) {
throw new InvalidInputException();
}
}
@Override
protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException {
try {
// Create method ref
MethodRef methodRef = this.ast.newMethodRef();
SimpleName methodName = new SimpleName(this.ast);
int length = this.identifierLengthStack[0] - 1; // may be > 0 for member class constructor reference
methodName.internalSetIdentifier(new String(this.identifierStack[length]));
methodRef.setName(methodName);
int start = (int) (this.identifierPositionStack[length] >>> 32);
int end = (int) this.identifierPositionStack[length];
methodName.setSourceRange(start, end - start + 1);
// Set qualifier
if (receiver == null) {
start = this.memberStart;
methodRef.setSourceRange(start, end - start + 1);
} else {
Name typeRef = (Name) receiver;
methodRef.setQualifier(typeRef);
start = typeRef.getStartPosition();
}
// Add arguments
if (arguments != null) {
Iterator parameters = arguments.listIterator();
while (parameters.hasNext()) {
MethodRefParameter param = (MethodRefParameter) parameters.next();
methodRef.parameters().add(param);
}
}
methodRef.setSourceRange(start, this.scanner.getCurrentTokenEndPosition()-start+1);
return methodRef;
}
catch (ClassCastException ex) {
throw new InvalidInputException();
}
}
@Override
protected void createTag() {
int position = this.scanner.currentPosition;
this.scanner.resetTo(this.tagSourceStart, this.tagSourceEnd);
StringBuilder tagName = new StringBuilder();
int start = this.tagSourceStart;
this.scanner.getNextChar();
while (this.scanner.currentPosition <= (this.tagSourceEnd+1)) {
tagName.append(this.scanner.currentCharacter);
this.scanner.getNextChar();
}
if (TagElement.TAG_SNIPPET.equals(tagName.toString())) {
this.tagSourceEnd = this.index;
// need to use createSnippetTag to create @snippet
return;
}
TagElement tagElement = this.ast.newTagElement();
tagElement.setTagName(tagName.toString());
if (this.inlineTagStarted) {
start = this.inlineTagStart;
TagElement previousTag = null;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
previousTag.setSourceRange(start, this.tagSourceEnd-start+1);
pushOnAstStack(previousTag, true);
} else {
previousTag = (TagElement) this.astStack[this.astPtr];
}
int previousStart = previousTag.getStartPosition();
previousTag.fragments().add(tagElement);
previousTag.setSourceRange(previousStart, this.tagSourceEnd-previousStart+1);
} else {
pushOnAstStack(tagElement, true);
}
tagElement.setSourceRange(start, this.tagSourceEnd-start+1);
this.scanner.resetTo(position, this.javadocEnd);
}
@Override
protected Object createSnippetTag() {
TagElement tagElement = this.ast.newTagElement();
int position = this.scanner.currentPosition;
this.scanner.resetTo(this.tagSourceStart, this.tagSourceEnd);
StringBuilder tagName = new StringBuilder();
int start = this.tagSourceStart;
this.scanner.getNextChar();
while (this.scanner.currentPosition <= (this.tagSourceEnd+1)) {
tagName.append(this.scanner.currentCharacter);
this.scanner.getNextChar();
}
tagElement.setTagName(tagName.toString());
if (this.inlineTagStarted) {
start = this.inlineTagStart;
TagElement previousTag = null;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
previousTag.setSourceRange(start, this.tagSourceEnd-start+1);
pushOnAstStack(previousTag, true);
} else {
previousTag = (TagElement) this.astStack[this.astPtr];
}
int previousStart = previousTag.getStartPosition();
previousTag.fragments().add(tagElement);
previousTag.setSourceRange(previousStart, this.tagSourceEnd-previousStart+1);
} else {
pushOnAstStack(tagElement, true);
}
tagElement.setSourceRange(start, this.tagSourceEnd-start+1);
this.scanner.resetTo(position, this.javadocEnd);
return tagElement;
}
@Override
protected void setSnippetIsValid(Object tag, boolean value) {
if (tag instanceof TagElement) {
((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_IS_VALID, value);
}
}
@Override
protected void setSnippetError(Object tag, String value) {
if (tag instanceof TagElement) {
((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_ERROR, value);
}
}
@Override
protected void setSnippetID(Object tag, String value) {
if (tag instanceof TagElement) {
((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_ID, value);
}
}
@Override
protected Object createSnippetRegion(String name, List<Object> tags, Object snippetTag, boolean isDummyRegion, boolean considerPrevTag) {
if (!isDummyRegion) {
return createSnippetOriginalRegion(name, tags);
}
List<TagElement> tagsToBeProcessed = new ArrayList<>();
Object toBeReturned = null;
if (tags != null && tags.size() > 0) {
int start = -1;
int end = -1;
for (Object tag : tags) {
if (tag instanceof TagElement) {
TagElement tagElem = (TagElement) tag;
tagsToBeProcessed.add(tagElem);
int tagStart = tagElem.getStartPosition();
int tagEnd = tagStart + tagElem.getLength();
if (start == -1 || start > tagStart) {
start = tagStart;
}
if (end ==-1 || end < tagEnd) {
end = tagEnd;
}
} else if (tag instanceof JavaDocRegion && snippetTag instanceof TagElement) {
TagElement snippet = (TagElement) snippetTag;
JavaDocRegion reg = (JavaDocRegion) tag;
if (!reg.isDummyRegion()) {
snippet.fragments().add(reg);
}
toBeReturned = reg;
}
}
if (tagsToBeProcessed.size() > 0) {
boolean process = true;
if (considerPrevTag && snippetTag instanceof TagElement) {
TagElement snippetTagElem = (TagElement) snippetTag;
Object prevTag = snippetTagElem.fragments().get(snippetTagElem.fragments().size() - 1);
if (prevTag instanceof TagElement) {
tagsToBeProcessed.add(0, (TagElement)prevTag);
snippetTagElem.fragments().remove(prevTag);
} else if (prevTag instanceof JavaDocRegion) {
JavaDocRegion region = (JavaDocRegion) prevTag;
region.tags().addAll(tagsToBeProcessed);
toBeReturned = snippetTag;
process = false;
}
}
if (process) {
if (tagsToBeProcessed.size() == 1) {
return tagsToBeProcessed.get(0);
}
else {
JavaDocRegion region = this.ast.newJavaDocRegion();
region.tags().addAll(tagsToBeProcessed);
region.setSourceRange(start, end);
toBeReturned = region;
}
}
} else {
toBeReturned = snippetTag;
}
}
return toBeReturned;
}
private Object createSnippetOriginalRegion(String name, List<Object> tags) {
JavaDocRegion region = this.ast.newJavaDocRegion();
if (tags != null && tags.size() > 0) {
int start = -1;
int end = -1;
for (Object tag : tags) {
if (tag instanceof TagElement) {
TagElement tagElem = (TagElement) tag;
region.tags().add(tagElem);
int tagStart = tagElem.getStartPosition();
int tagEnd = tagStart + tagElem.getLength();
if (start == -1 || start > tagStart) {
start = tagStart;
}
if (end ==-1 || end < tagEnd) {
end = tagEnd;
}
}
}
region.setSourceRange(start, end-start);
region.setDummyRegion(false);
}
if (name != null) {
region.setTagName(name);
}
return region;
}
@Override
protected Object createSnippetInnerTag(String tagName, int start, int end) {
if (tagName != null) {
TagElement tagElement = this.ast.newTagElement();
tagElement.setTagName(tagName.toString());
if (this.astPtr == -1) {
return null;
}
tagElement.setSourceRange(start, end-start);
return tagElement;
}
return null;
}
@Override
protected void closeJavaDocRegion(String name, Object snippetTag, int end) {
if (snippetTag instanceof TagElement) {
TagElement snippet = (TagElement) snippetTag;
List<JavaDocRegion> regions = snippet.tagRegions();
JavaDocRegion regionToClose = null;
if (name != null) {
for (JavaDocRegion region : regions) {
if (name.equals(region.getTagName())) {
if (!this.isRegionToBeEnded(region)
&& !this.hasRegionEnded(region)) {
regionToClose = region;
break;
}
}
}
} else {
for (int i= regions.size()-1; i >-1; i--) {
JavaDocRegion region = regions.get(i);
if (!this.isRegionToBeEnded(region)
&& !this.hasRegionEnded(region)) {
regionToClose = region;
break;
}
}
}
if (regionToClose != null) {
setRegionToBeEnded(regionToClose, true);
int start = regionToClose.getStartPosition();
int curEnd = start + regionToClose.getLength();
if (end > curEnd) {
regionToClose.setSourceRange(start, end-start);
}
}
}
}
@Override
protected void addTagProperties(Object tag, Map<String, Object> map, int tagCount) {
if (tag instanceof TagElement) {
TagElement tagElement = (TagElement) tag;
map.forEach((k, v) -> {
TagProperty tagProperty = this.ast.newTagProperty();
tagProperty.setName(k);
if (v instanceof String) {
tagProperty.setStringValue((String)v);
} else if (v instanceof ASTNode) {
tagProperty.setNodeValue((ASTNode)v);
}
tagElement.tagProperties().add(tagProperty);
});
tagElement.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_INLINE_TAG_COUNT, tagCount);
}
}
@Override
protected void addSnippetInnerTag(Object obj, Object snippetTag) {
boolean isNotDummyRegion = false;
if (obj instanceof JavaDocRegion) {
JavaDocRegion region = (JavaDocRegion) obj;
if (snippetTag instanceof TagElement) {
TagElement snippetTagElem = (TagElement) snippetTag;
if (!region.isDummyRegion()) {
snippetTagElem.fragments().add(region);
isNotDummyRegion = true;
} else {
Iterator<Object> itr = region.tags().iterator();
while (itr.hasNext()) {
Object tag = itr.next();
if (tag instanceof JavaDocRegion) {
JavaDocRegion reg = (JavaDocRegion) tag;
if (!reg.isDummyRegion()) {
region.tags().remove(reg);
snippetTagElem.fragments().add(reg);
}
}
}
}
}
}
if (obj instanceof AbstractTagElement && !isNotDummyRegion) {
AbstractTagElement tagElement = (AbstractTagElement) obj;
AbstractTagElement previousTag = null;
if (this.astPtr == -1) {
return;
} else {
previousTag = (AbstractTagElement) this.astStack[this.astPtr];
List fragments = previousTag.fragments();
if (this.inlineTagStarted) {
int size = fragments.size();
if (size == 0) {
//do nothing
} else {
// If last fragment is a tag, then use it as previous tag
ASTNode lastFragment = (ASTNode) fragments.get(size-1);
if (lastFragment instanceof AbstractTagElement) {
previousTag = (AbstractTagElement) lastFragment;
}
}
}
}
previousTag.fragments().add(tagElement);
}
}
@Override
protected Object createTypeReference(int primitiveToken, boolean canBeModule) {
return createTypeReference(primitiveToken);
}
@Override
protected Object createTypeReference(int primitiveToken) {
int size = this.identifierLengthStack[this.identifierLengthPtr];
String[] identifiers = new String[size];
int pos = this.identifierPtr - size + 1;
for (int i = 0; i < size; i++) {
identifiers[i] = new String(this.identifierStack[pos+i]);
}
ASTNode typeRef = null;
if (primitiveToken == -1) {
typeRef = this.ast.internalNewName(identifiers);
} else {
switch (primitiveToken) {
case TerminalTokens.TokenNamevoid :
typeRef = this.ast.newPrimitiveType(PrimitiveType.VOID);
break;
case TerminalTokens.TokenNameboolean :
typeRef = this.ast.newPrimitiveType(PrimitiveType.BOOLEAN);
break;
case TerminalTokens.TokenNamebyte :
typeRef = this.ast.newPrimitiveType(PrimitiveType.BYTE);
break;
case TerminalTokens.TokenNamechar :
typeRef = this.ast.newPrimitiveType(PrimitiveType.CHAR);
break;
case TerminalTokens.TokenNamedouble :
typeRef = this.ast.newPrimitiveType(PrimitiveType.DOUBLE);
break;
case TerminalTokens.TokenNamefloat :
typeRef = this.ast.newPrimitiveType(PrimitiveType.FLOAT);
break;
case TerminalTokens.TokenNameint :
typeRef = this.ast.newPrimitiveType(PrimitiveType.INT);
break;
case TerminalTokens.TokenNamelong :
typeRef = this.ast.newPrimitiveType(PrimitiveType.LONG);
break;
case TerminalTokens.TokenNameshort :
typeRef = this.ast.newPrimitiveType(PrimitiveType.SHORT);
break;
default:
// should not happen
return null;
}
}
// Update ref for whole name
int start = (int) (this.identifierPositionStack[pos] >>> 32);
// int end = (int) this.identifierPositionStack[this.identifierPtr];
// typeRef.setSourceRange(start, end-start+1);
// Update references of each simple name
if (size > 1) {
Name name = (Name)typeRef;
int nameIndex = size;
for (int i=this.identifierPtr; i>pos; i--, nameIndex--) {
int s = (int) (this.identifierPositionStack[i] >>> 32);
int e = (int) this.identifierPositionStack[i];
name.index = nameIndex;
SimpleName simpleName = ((QualifiedName)name).getName();
simpleName.index = nameIndex;
simpleName.setSourceRange(s, e-s+1);
name.setSourceRange(start, e-start+1);
name = ((QualifiedName)name).getQualifier();
}
int end = (int) this.identifierPositionStack[pos];
name.setSourceRange(start, end-start+1);
name.index = nameIndex;
} else {
int end = (int) this.identifierPositionStack[pos];
typeRef.setSourceRange(start, end-start+1);
}
return typeRef;
}
private ModuleQualifiedName createModuleReference(int moduleRefTokenCount) {
String[] identifiers = new String[moduleRefTokenCount];
for (int i = 0; i < moduleRefTokenCount; i++) {
identifiers[i] = new String(this.identifierStack[i]);
}
ModuleQualifiedName moduleRef = new ModuleQualifiedName(this.ast);
ASTNode typeRef = null;
typeRef = this.ast.internalNewName(identifiers);
int start = (int) (this.identifierPositionStack[0] >>> 32);
if (moduleRefTokenCount > 1) {
Name name = (Name)typeRef;
int nameIndex = moduleRefTokenCount;
for (int i=moduleRefTokenCount-1; i>0; i--, nameIndex--) {
int s = (int) (this.identifierPositionStack[i] >>> 32);
int e = (int) this.identifierPositionStack[i];
name.index = nameIndex;
SimpleName simpleName = ((QualifiedName)name).getName();
simpleName.index = nameIndex;
simpleName.setSourceRange(s, e-s+1);
name.setSourceRange(start, e-start+1);
name = ((QualifiedName)name).getQualifier();
}
int end = (int) this.identifierPositionStack[0];
name.setSourceRange(start, end-start+1);
name.index = nameIndex;
} else {
int end = (int) this.identifierPositionStack[0];
typeRef.setSourceRange(start, end-start+1);
}
moduleRef.setModuleQualifier((Name)typeRef);
moduleRef.setName(null);
moduleRef.setSourceRange(typeRef.getStartPosition(), typeRef.getLength()+1);
return moduleRef;
}
@Override
protected Object createModuleTypeReference(int primitiveToken, int moduleRefTokenCount) {
int size = this.identifierLengthStack[this.identifierLengthPtr];
ModuleQualifiedName moduleRef= null;
Name typeRef= null;
if (size == moduleRefTokenCount) {
moduleRef= createModuleReference(moduleRefTokenCount);
this.lastIdentifierEndPosition++;
} else {
String[] moduleIdentifiers = new String[moduleRefTokenCount];
String[] identifiers = new String[size- moduleRefTokenCount];
int pos = this.identifierPtr - size + 1;
for (int i = 0; i < size; i++) {
if (i < moduleRefTokenCount) {
moduleIdentifiers[i] = new String(this.identifierStack[pos+i]);
} else {
identifiers[i-moduleRefTokenCount] = new String(this.identifierStack[pos+i]);
}
}
moduleRef= createModuleReference(moduleRefTokenCount);
pos = this.identifierPtr+moduleRefTokenCount - size + 1;
if (primitiveToken == -1) {
typeRef = this.ast.internalNewName(identifiers);
// Update ref for whole name
int start = (int) (this.identifierPositionStack[pos] >>> 32);
// int end = (int) this.identifierPositionStack[this.identifierPtr];
// typeRef.setSourceRange(start, end-start+1);
// Update references of each simple name
if (size-moduleRefTokenCount > 1) {
Name name = typeRef;
int nameIndex = size-moduleRefTokenCount;
for (int i=this.identifierPtr; i>pos; i--, nameIndex--) {
int s = (int) (this.identifierPositionStack[i] >>> 32);
int e = (int) this.identifierPositionStack[i];
name.index = nameIndex;
SimpleName simpleName = ((QualifiedName)name).getName();
simpleName.index = nameIndex;
simpleName.setSourceRange(s, e-s+1);
name.setSourceRange(start, e-start+1);
name = ((QualifiedName)name).getQualifier();
}
int end = (int) this.identifierPositionStack[pos];
name.setSourceRange(start, end-start+1);
name.index = nameIndex;
} else {
int end = (int) this.identifierPositionStack[pos];
typeRef.setSourceRange(start, end-start+1);
}
moduleRef.setName(typeRef);
moduleRef.setSourceRange(moduleRef.getStartPosition(), moduleRef.getLength() + typeRef.getLength());
}
}
return moduleRef;
}
@Override
protected boolean parseIdentifierTag(boolean report) {
if (super.parseIdentifierTag(report)) {
createTag();
this.index = this.tagSourceEnd+1;
this.scanner.resetTo(this.index, this.javadocEnd);
return true;
}
return false;
}
/*
* Parse @return tag declaration
*/
protected boolean parseReturn() {
createTag();
return true;
}
@Override
protected boolean parseTag(int previousPosition) throws InvalidInputException {
// Read tag name
int currentPosition = this.index;
int token = readTokenAndConsume();
char[] tagName = CharOperation.NO_CHAR;
if (currentPosition == this.scanner.startPosition) {
this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
tagName = this.scanner.getCurrentIdentifierSource();
} else {
this.tagSourceEnd = currentPosition-1;
}
// Try to get tag name other than java identifier
// (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
if (this.scanner.currentCharacter != ' ' && !ScannerHelper.isWhitespace(this.scanner.currentCharacter)) {
tagNameToken: while (token != TerminalTokens.TokenNameEOF && this.index < this.scanner.eofPosition) {
int length = tagName.length;
// !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names
switch (this.scanner.currentCharacter) {
case '}':
case '*': // break for '*' as this is perhaps the end of comment (bug 65288)
case '!':
case '#':
case '%':
case '&':
case '\'':
case '"':
case ':':
case '<':
case '>':
break tagNameToken;
case '-': // allowed in tag names as this character is often used in doclets (bug 68087)
System.arraycopy(tagName, 0, tagName = new char[length+1], 0, length);
tagName[length] = this.scanner.currentCharacter;
break;
default:
if (this.scanner.currentCharacter == ' ' || ScannerHelper.isWhitespace(this.scanner.currentCharacter)) {
break tagNameToken;
}
token = readTokenAndConsume();
char[] ident = this.scanner.getCurrentIdentifierSource();
System.arraycopy(tagName, 0, tagName = new char[length+ident.length], 0, length);
System.arraycopy(ident, 0, tagName, length, ident.length);
break;
}
this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
this.scanner.getNextChar();
this.index = this.scanner.currentPosition;
}
}
int length = tagName.length;
this.index = this.tagSourceEnd+1;
this.scanner.currentPosition = this.tagSourceEnd+1;
this.tagSourceStart = previousPosition;
// tage name may be empty (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=125903)
if (tagName.length == 0) {
return false;
}
// Decide which parse to perform depending on tag name
this.tagValue = NO_TAG_VALUE;
boolean valid = true;
switch (token) {
case TerminalTokens.TokenNameIdentifier :
switch (tagName[0]) {
case 'c':
if (length == TAG_CATEGORY_LENGTH && CharOperation.equals(TAG_CATEGORY, tagName)) {
this.tagValue = TAG_CATEGORY_VALUE;
valid = parseIdentifierTag(false); // TODO (frederic) reconsider parameter value when @category will be significant in spec
} else if (length == TAG_CODE_LENGTH && CharOperation.equals(TAG_CODE, tagName)) {
this.tagValue = TAG_CODE_VALUE;
createTag();
} else {
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case 'd':
if (length == TAG_DEPRECATED_LENGTH && CharOperation.equals(TAG_DEPRECATED, tagName)) {
this.deprecated = true;
this.tagValue = TAG_DEPRECATED_VALUE;
} else {
this.tagValue = TAG_OTHERS_VALUE;
}
createTag();
break;
case 'i':
if (length == TAG_INHERITDOC_LENGTH && CharOperation.equals(TAG_INHERITDOC, tagName)) {
if (this.reportProblems) {
recordInheritedPosition((((long) this.tagSourceStart) << 32) + this.tagSourceEnd);
}
this.tagValue = TAG_INHERITDOC_VALUE;
} else {
this.tagValue = TAG_OTHERS_VALUE;
}
createTag();
break;
case 'p':
if (length == TAG_PARAM_LENGTH && CharOperation.equals(TAG_PARAM, tagName)) {
this.tagValue = TAG_PARAM_VALUE;
valid = parseParam();
} else {
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case 'e':
if (length == TAG_EXCEPTION_LENGTH && CharOperation.equals(TAG_EXCEPTION, tagName)) {
this.tagValue = TAG_EXCEPTION_VALUE;
valid = parseThrows();
} else {
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case 's':
if (length == TAG_SEE_LENGTH && CharOperation.equals(TAG_SEE, tagName)) {
this.tagValue = TAG_SEE_VALUE;
if (this.inlineTagStarted) {
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
// Cannot have @see inside inline comment
valid = false;
} else {
valid = parseReference(true);
}
} else if (length == TAG_SNIPPET_LENGTH && CharOperation.equals(TAG_SNIPPET, tagName)) {
this.tagValue = TAG_SNIPPET_LENGTH;
if (!this.inlineTagStarted) {
// @snippet is an inline comment
valid = false;
} else {
this.tagValue = TAG_SNIPPET_VALUE;
valid = parseSnippet();
}
} else {
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case 'l':
if (length == TAG_LINK_LENGTH && CharOperation.equals(TAG_LINK, tagName)) {
this.tagValue = TAG_LINK_VALUE;
} else if (length == TAG_LINKPLAIN_LENGTH && CharOperation.equals(TAG_LINKPLAIN, tagName)) {
this.tagValue = TAG_LINKPLAIN_VALUE;
} else if (length == TAG_LITERAL_LENGTH && CharOperation.equals(TAG_LITERAL, tagName)) {
this.tagValue = TAG_LITERAL_VALUE;
}
if (this.tagValue != NO_TAG_VALUE && this.tagValue != TAG_LITERAL_VALUE) {
if (this.inlineTagStarted) {
valid = parseReference(true);
} else {
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
// Cannot have @link outside inline comment
valid = false;
}
} else {
if (this.tagValue == NO_TAG_VALUE) this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case 'v':
if (this.sourceLevel >= ClassFileConstants.JDK1_5 && length == TAG_VALUE_LENGTH && CharOperation.equals(TAG_VALUE, tagName)) {
this.tagValue = TAG_VALUE_VALUE;
if (this.inlineTagStarted) {
valid = parseReference();
} else {
valid = false;
}
} else {
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
default:
this.tagValue = TAG_OTHERS_VALUE;
createTag();
}
break;
case TerminalTokens.TokenNamereturn :
this.tagValue = TAG_RETURN_VALUE;
valid = parseReturn();
break;
case TerminalTokens.TokenNamethrows :
this.tagValue = TAG_THROWS_VALUE;
valid = parseThrows();
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:
case TerminalTokens.TokenNameenum :
case TerminalTokens.TokenNameconst :
case TerminalTokens.TokenNamegoto :
this.tagValue = TAG_OTHERS_VALUE;
createTag();
break;
}
this.textStart = this.index;
return valid;
}
@Override
protected boolean pushParamName(boolean isTypeParam) {
int idIndex = isTypeParam ? 1 : 0;
final SimpleName name = new SimpleName(this.ast);
name.internalSetIdentifier(new String(this.identifierStack[idIndex]));
int nameStart = (int) (this.identifierPositionStack[idIndex] >>> 32);
int nameEnd = (int) (this.identifierPositionStack[idIndex] & 0x00000000FFFFFFFFL);
name.setSourceRange(nameStart, nameEnd-nameStart+1);
TagElement paramTag = this.ast.newTagElement();
paramTag.setTagName(TagElement.TAG_PARAM);
if (isTypeParam) { // specific storage for @param <E> (see bug 79809)
// '<' was stored in identifiers stack
TextElement text = this.ast.newTextElement();
text.setText(new String(this.identifierStack[0]));
int txtStart = (int) (this.identifierPositionStack[0] >>> 32);
int txtEnd = (int) (this.identifierPositionStack[0] & 0x00000000FFFFFFFFL);
text.setSourceRange(txtStart, txtEnd-txtStart+1);
paramTag.fragments().add(text);
// add simple name
paramTag.fragments().add(name);
// '>' was stored in identifiers stack
text = this.ast.newTextElement();
text.setText(new String(this.identifierStack[2]));
txtStart = (int) (this.identifierPositionStack[2] >>> 32);
txtEnd = (int) (this.identifierPositionStack[2] & 0x00000000FFFFFFFFL);
text.setSourceRange(txtStart, txtEnd-txtStart+1);
paramTag.fragments().add(text);
// set param tag source range
paramTag.setSourceRange(this.tagSourceStart, txtEnd-this.tagSourceStart+1);
} else {
paramTag.setSourceRange(this.tagSourceStart, nameEnd-this.tagSourceStart+1);
paramTag.fragments().add(name);
}
pushOnAstStack(paramTag, true);
return true;
}
@Override
protected boolean pushSeeRef(Object statement) {
TagElement seeTag = this.ast.newTagElement();
ASTNode node = (ASTNode) statement;
seeTag.fragments().add(node);
int end = node.getStartPosition()+node.getLength()-1;
if (this.inlineTagStarted) {
seeTag.setSourceRange(this.inlineTagStart, end-this.inlineTagStart+1);
switch (this.tagValue) {
case TAG_LINK_VALUE:
seeTag.setTagName(TagElement.TAG_LINK);
break;
case TAG_LINKPLAIN_VALUE:
seeTag.setTagName(TagElement.TAG_LINKPLAIN);
break;
case TAG_VALUE_VALUE:
seeTag.setTagName(TagElement.TAG_VALUE);
break;
}
TagElement previousTag = null;
int previousStart = this.inlineTagStart;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
pushOnAstStack(previousTag, true);
} else {
previousTag = (TagElement) this.astStack[this.astPtr];
previousStart = previousTag.getStartPosition();
}
previousTag.fragments().add(seeTag);
previousTag.setSourceRange(previousStart, end-previousStart+1);
} else {
seeTag.setTagName(TagElement.TAG_SEE);
seeTag.setSourceRange(this.tagSourceStart, end-this.tagSourceStart+1);
pushOnAstStack(seeTag, true);
}
return true;
}
@Override
protected void pushText(int start, int end) {
// Create text element
TextElement text = this.ast.newTextElement();
text.setText(new String( this.source, start, end-start));
text.setSourceRange(start, end-start);
// Search previous tag on which to add the text element
TagElement previousTag = null;
int previousStart = start;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
previousTag.setSourceRange(start, end-start);
pushOnAstStack(previousTag, true);
} else {
previousTag = (TagElement) this.astStack[this.astPtr];
previousStart = previousTag.getStartPosition();
}
// If we're in a inline tag, then retrieve previous tag in its fragments
List fragments = previousTag.fragments();
if (this.inlineTagStarted) {
int size = fragments.size();
if (size == 0) {
// no existing fragment => just add the element
TagElement inlineTag = this.ast.newTagElement();
fragments.add(inlineTag);
previousTag = inlineTag;
} else {
// If last fragment is a tag, then use it as previous tag
ASTNode lastFragment = (ASTNode) fragments.get(size-1);
if (lastFragment.getNodeType() == ASTNode.TAG_ELEMENT) {
previousTag = (TagElement) lastFragment;
previousStart = previousTag.getStartPosition();
}
}
}
// Add the text
previousTag.fragments().add(text);
previousTag.setSourceRange(previousStart, end-previousStart);
this.textStart = -1;
}
@Override
protected void pushSnippetText(int start, int end, boolean addNewLine, Object snippetTag) {
// Create text element
TextElement text = this.ast.newTextElement();
String textToBeAdded= new String( this.source, start, end-start);
int iindex = textToBeAdded.indexOf('*');
if (iindex > -1 && textToBeAdded.substring(0, iindex+1).trim().equals("*")) { //$NON-NLS-1$
textToBeAdded = textToBeAdded.substring(iindex+1);
if (addNewLine) {
textToBeAdded += System.lineSeparator();
}
}
text.setText(textToBeAdded);
text.setSourceRange(start, end-start);
// Search previous tag on which to add the text element
AbstractTagElement previousTag = null;
int previousStart = start;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
previousTag.setSourceRange(start, end-start);
pushOnAstStack(previousTag, true);
} else {
previousTag = (AbstractTagElement) this.astStack[this.astPtr];
previousStart = previousTag.getStartPosition();
}
AbstractTagElement prevTag = null;
// If we're in a inline tag, then retrieve previous tag in its fragments
List fragments = previousTag.fragments();
if (this.inlineTagStarted) {
int size = fragments.size();
if (size == 0) {
//do nothing
} else {
// If last fragment is a tag, then use it as previous tag
ASTNode lastFragment = (ASTNode) fragments.get(size-1);
if (lastFragment instanceof AbstractTagElement) {
previousTag = (AbstractTagElement) lastFragment;
previousStart = previousTag.getStartPosition();
if (this.snippetInlineTagStarted) {
fragments = previousTag.fragments();
size = fragments.size();
if (size == 0) {
//do nothing
} else {
lastFragment = (ASTNode) fragments.get(size-1);
if (lastFragment instanceof AbstractTagElement) {
prevTag = (AbstractTagElement) lastFragment;
this.snippetInlineTagStarted = false;
}
}
this.snippetInlineTagStarted = false;
}
}
}
}
int finEnd = end;
boolean isNotDummyJavaDocRegion = false;
if (prevTag instanceof JavaDocRegion && !((JavaDocRegion)prevTag).isDummyRegion()) {
isNotDummyJavaDocRegion = true;
}
// Add the text
if (prevTag != null && !isNotDummyJavaDocRegion) {
prevTag.fragments().add(text);
int curStart = prevTag.getStartPosition();
int curEnd = curStart + prevTag.getLength();
int finStart = start;
if (curStart < start) {
finStart = curStart;
}
if (curEnd > end) {
finEnd = curEnd;
}
prevTag.setSourceRange(finStart, finEnd - finStart);
} else {
previousTag.fragments().add(text);
}
previousTag.setSourceRange(previousStart, finEnd-previousStart);
this.textStart = -1;
if (snippetTag instanceof TagElement) {
fragments = ((TagElement) snippetTag).fragments();
for (Object frag : fragments) {
if (frag instanceof JavaDocRegion) {
JavaDocRegion region = (JavaDocRegion) frag;
if (!region.isDummyRegion() && !hasRegionEnded(region)) {
int startPos = region.getStartPosition();
int endPos = startPos + region.getLength();
if (startPos > start) {
startPos = start;
}
if (endPos < end) {
endPos = end;
}
Object textVal = region.getProperty(TagProperty.TAG_PROPERTY_SNIPPET_REGION_TEXT);
if (!(textVal instanceof TextElement)) {
region.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_REGION_TEXT, text);
}
region.setSourceRange(startPos, endPos-startPos);
if (isRegionToBeEnded(region)) {
setRegionEnded(region, true);
}
}
}
}
}
}
private boolean hasRegionEnded(JavaDocRegion region) {
boolean ended = false;
if (region != null) {
Object value = region.getProperty(JavaDocRegion.REGION_ENDED);
if (value instanceof Boolean && ((Boolean)value).booleanValue()) {
ended = true;
}
}
return ended;
}
private boolean isRegionToBeEnded(JavaDocRegion region) {
boolean toBeEnded = false;
if (region != null) {
Object value = region.getProperty(JavaDocRegion.REGION_TO_BE_ENDED);
if (value instanceof Boolean && ((Boolean)value).booleanValue()) {
toBeEnded = true;
}
}
return toBeEnded;
}
private void setRegionToBeEnded(JavaDocRegion region, boolean value) {
if (region != null) {
region.setProperty(JavaDocRegion.REGION_TO_BE_ENDED, value);
}
}
private void setRegionEnded(JavaDocRegion region, boolean value) {
if (region != null) {
region.setProperty(JavaDocRegion.REGION_ENDED, value);
setRegionToBeEnded(region, !value);
}
}
@Override
protected void pushExternalSnippetText(String text,int start, int end) {
String snippetLangHeader = "<pre>"; //$NON-NLS-1$ //the code snippets comes as preformatted so need to prefix them with <pre> tag
String snipperLangFooter = "</pre>"; //$NON-NLS-1$
text = snippetLangHeader + text + snipperLangFooter;
TextElement textElement = this.ast.newTextElement();
textElement.setText(text);
textElement.setSourceRange(start, end-start);
// Search previous tag on which to add the text element
TagElement previousTag = null;
int previousStart = start;
int previousEnd = end;
if (this.astPtr == -1) {
previousTag = this.ast.newTagElement();
previousTag.setSourceRange(start, end-start);
pushOnAstStack(previousTag, true);
} else {
previousTag = (TagElement) this.astStack[this.astPtr];
previousStart = previousTag.getStartPosition();
previousEnd = previousStart + previousTag.getLength();
}
previousTag.fragments().add(textElement);
int curStart = previousStart;
int curEnd = previousEnd;
if (start < previousStart) {
curStart = start;
}
if (end > previousEnd) {
curEnd = end;
}
previousTag.setSourceRange(curStart, curEnd-curStart);
this.textStart = -1;
}
@Override
protected boolean pushThrowName(Object typeRef) {
TagElement throwsTag = this.ast.newTagElement();
switch (this.tagValue) {
case TAG_THROWS_VALUE:
throwsTag.setTagName(TagElement.TAG_THROWS);
break;
case TAG_EXCEPTION_VALUE:
throwsTag.setTagName(TagElement.TAG_EXCEPTION);
break;
}
throwsTag.setSourceRange(this.tagSourceStart, this.scanner.getCurrentTokenEndPosition()-this.tagSourceStart+1);
throwsTag.fragments().add(typeRef);
pushOnAstStack(throwsTag, true);
return true;
}
@Override
protected void refreshInlineTagPosition(int previousPosition) {
if (this.astPtr != -1) {
TagElement previousTag = (TagElement) this.astStack[this.astPtr];
if (this.inlineTagStarted) {
int previousStart = previousTag.getStartPosition();
previousTag.setSourceRange(previousStart, previousPosition-previousStart+1);
if (previousTag.fragments().size() > 0) {
ASTNode inlineTag = (ASTNode) previousTag.fragments().get(previousTag.fragments().size()-1);
if (inlineTag.getNodeType() == ASTNode.TAG_ELEMENT) {
int inlineStart = inlineTag.getStartPosition();
inlineTag.setSourceRange(inlineStart, previousPosition-inlineStart+1);
}
}
}
}
}
/*
* Add stored tag elements to associated comment.
*/
@Override
protected void updateDocComment() {
for (int idx = 0; idx <= this.astPtr; idx++) {
this.docComment.tags().add(this.astStack[idx]);
}
}
@Override
protected boolean areRegionsClosed() {
// do nothing
return true;
}
@Override
protected void setRegionPosition(int currentPosition) {
// do nothing
}
}