blob: b98cda5dc1f61f26d66d4d5df00725806d819611 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.formatter;
import java.util.List;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.parser.JavadocParser;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.jdt.internal.formatter.comment.IJavaDocTagConstants;
/**
* Internal parser used for formatting javadoc comments.
*/
public class FormatterCommentParser extends JavadocParser implements IJavaDocTagConstants {
char[][] htmlTags;
int htmlTagsPtr = -1;
int inlineHtmlTagsPtr = -1;
private boolean invalidTagName;
public boolean parseHtmlTags;
public FormatterCommentParser(long sourceLevel) {
super(null);
this.kind = FORMATTER_COMMENT_PARSER | TEXT_PARSE;
this.reportProblems = false;
this.checkDocComment = true;
this.sourceLevel = sourceLevel;
}
public boolean parse(int start, int end) {
// Init
this.javadocStart = start;
this.javadocEnd = end;
this.firstTagPosition = this.javadocStart;
// Need to flush html tags stack in case of unclosed ones in previous javadoc comments
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=239941
this.htmlTagsPtr = -1;
// parse comment
boolean valid = commentParse();
return valid && this.docComment != null;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#createArgumentReference(char[], int, boolean, java.lang.Object, long[], long)
*/
protected Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object ref, long[] dimPositions, long argNamePos) throws InvalidInputException {
FormatJavadocReference typeRef = (FormatJavadocReference) ref;
if (dim > 0) {
typeRef.sourceEnd = (int) dimPositions[dim-1];
}
if (argNamePos >= 0) typeRef.sourceEnd = (int) argNamePos;
return ref;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createFakeReference(int)
*/
protected boolean createFakeReference(int start) {
// synch scanner and parser positions
this.scanner.currentPosition = this.index;
// create reference in order to be able to format it
int lineStart = this.scanner.getLineNumber(start);
FormatJavadocReference reference = new FormatJavadocReference(start, this.index-1, lineStart);
return pushSeeRef(reference);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#createFieldReference(java.lang.Object)
*/
protected Object createFieldReference(Object receiver) throws InvalidInputException {
int start = receiver == null ? this.memberStart : ((FormatJavadocReference)receiver).sourceStart;
int lineStart = this.scanner.getLineNumber(start);
return new FormatJavadocReference(start, (int) this.identifierPositionStack[0], lineStart);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#createMethodReference(java.lang.Object, java.util.List)
*/
protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException {
int start = receiver == null ? this.memberStart : ((FormatJavadocReference) receiver).sourceStart;
int lineStart = this.scanner.getLineNumber(start);
return new FormatJavadocReference(start, this.scanner.getCurrentTokenEndPosition(), lineStart);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#createTag()
*/
protected void createTag() {
int lineStart = this.scanner.getLineNumber(this.tagSourceStart);
if (this.inlineTagStarted) {
FormatJavadocBlock block = new FormatJavadocBlock(this.inlineTagStart, this.tagSourceEnd, lineStart, this.tagValue);
FormatJavadocBlock previousBlock = null;
if (this.astPtr == -1) {
previousBlock = new FormatJavadocBlock(this.inlineTagStart, this.tagSourceEnd, lineStart, NO_TAG_VALUE);
pushOnAstStack(previousBlock, true);
} else {
previousBlock = (FormatJavadocBlock) this.astStack[this.astPtr];
}
previousBlock.addBlock(block, this.htmlTagsPtr == -1 ? 0 : this.htmlTagsPtr);
} else {
FormatJavadocBlock block = new FormatJavadocBlock(this.tagSourceStart, this.tagSourceEnd, lineStart, this.tagValue);
pushOnAstStack(block, true);
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#createTypeReference(int)
*/
protected Object createTypeReference(int primitiveToken) {
int size = this.identifierLengthStack[this.identifierLengthPtr];
if (size == 0) return null;
int start = (int) (this.identifierPositionStack[this.identifierPtr] >>> 32);
int lineStart = this.scanner.getLineNumber(start);
if (size == 1) {
return new FormatJavadocReference(this.identifierPositionStack[this.identifierPtr], lineStart);
}
long[] positions = new long[size];
System.arraycopy(this.identifierPositionStack, this.identifierPtr - size + 1, positions, 0, size);
return new FormatJavadocReference((int) (positions[0] >>> 32), (int) positions[positions.length-1], lineStart);
}
/*
* Return the html tag index in the various arrays of IJavaDocTagConstants.
* The returned int is set as follow:
* - the array index is set on bits 0 to 7
* - the tag category is set on bit 8 to 15 (0xFF00 if no array includes the tag)
*/
private int getHtmlTagIndex(char[] htmlTag) {
int length = htmlTag == null ? 0 : htmlTag.length;
int tagId = 0;
if (length > 0) {
for (int i=0, max=JAVADOC_SINGLE_BREAK_TAG.length; i<max; i++) {
char[] tag = JAVADOC_SINGLE_BREAK_TAG[i];
if (length == tag.length && CharOperation.equals(htmlTag, tag, false)) {
return (tagId | JAVADOC_SINGLE_BREAK_TAG_ID) + i;
}
}
for (int i=0, max=JAVADOC_CODE_TAGS.length; i<max; i++) {
char[] tag = JAVADOC_CODE_TAGS[i];
if (length == tag.length && CharOperation.equals(htmlTag, tag, false)) {
return (tagId | JAVADOC_CODE_TAGS_ID) + i;
}
}
for (int i=0, max=JAVADOC_BREAK_TAGS.length; i<max; i++) {
char[] tag = JAVADOC_BREAK_TAGS[i];
if (length == tag.length && CharOperation.equals(htmlTag, tag, false)) {
return (tagId | JAVADOC_BREAK_TAGS_ID) + i;
}
}
for (int i=0, max=JAVADOC_IMMUTABLE_TAGS.length; i<max; i++) {
char[] tag = JAVADOC_IMMUTABLE_TAGS[i];
if (length == tag.length && CharOperation.equals(htmlTag, tag, false)) {
return (tagId | JAVADOC_IMMUTABLE_TAGS_ID) + i;
}
}
for (int i=0, max=JAVADOC_SEPARATOR_TAGS.length; i<max; i++) {
char[] tag = JAVADOC_SEPARATOR_TAGS[i];
if (length == tag.length && CharOperation.equals(htmlTag, tag, false)) {
return (tagId | JAVADOC_SEPARATOR_TAGS_ID) + i;
}
}
}
return JAVADOC_TAGS_ID_MASK;
}
/*
* Parse an HTML tag expected to be either opening (e.g. <tag_name> ) or
* closing (e.g. </tag_name>).
*/
protected boolean parseHtmlTag(int previousPosition, int endTextPosition) throws InvalidInputException {
if (!this.parseHtmlTags) return false;
boolean closing = false;
boolean valid = false;
boolean incremented = false;
int start = this.scanner.currentPosition;
int currentPosition = start;
int htmlPtr = this.htmlTagsPtr;
char firstChar = peekChar();
boolean hasWhitespaces = firstChar == ' ' || ScannerHelper.isWhitespace(firstChar);
try {
int token = readTokenAndConsume();
char[] htmlTag;
int htmlIndex;
switch (token) {
case TerminalTokens.TokenNameIdentifier:
// HTML tag opening
htmlTag = this.scanner.getCurrentIdentifierSource();
htmlIndex = getHtmlTagIndex(htmlTag);
if (htmlIndex == JAVADOC_TAGS_ID_MASK) return false;
if (htmlPtr >= 0) {
int lastHtmlTagIndex = getHtmlTagIndex(this.htmlTags[htmlPtr]);
if ((lastHtmlTagIndex & JAVADOC_TAGS_ID_MASK) == JAVADOC_IMMUTABLE_TAGS_ID) {
// Do not accept tags inside immutable tags except the <pre> tag
if ((htmlIndex & JAVADOC_TAGS_ID_MASK) == JAVADOC_CODE_TAGS_ID) {
FormatJavadocBlock previousBlock = (FormatJavadocBlock) this.astStack[this.astPtr];
FormatJavadocNode parentNode = previousBlock;
FormatJavadocNode lastNode = parentNode;
while (lastNode.getLastNode() != null) {
parentNode = lastNode;
lastNode = lastNode.getLastNode();
}
if (lastNode.isText()) {
FormatJavadocText text = (FormatJavadocText) lastNode;
if (text.separatorsPtr == -1) {
break;
}
}
}
return false;
}
}
if ((htmlIndex & JAVADOC_TAGS_ID_MASK) > JAVADOC_SINGLE_TAGS_ID) {
if (this.htmlTagsPtr == -1 || !CharOperation.equals(this.htmlTags[this.htmlTagsPtr], htmlTag, false)) {
if (++this.htmlTagsPtr == 0) { // lazy initialization
this.htmlTags = new char[AST_STACK_INCREMENT][];
} else { // resize if needed
if (this.htmlTagsPtr == this.htmlTags.length) {
System.arraycopy(this.htmlTags, 0, (this.htmlTags = new char[this.htmlTags.length + AST_STACK_INCREMENT][]), 0, this.htmlTagsPtr);
}
}
this.htmlTags[this.htmlTagsPtr] = htmlTag;
incremented = true;
}
}
// Accept xhtml syntax
currentPosition = this.scanner.currentPosition;
if (readToken() == TerminalTokens.TokenNameDIVIDE) {
consumeToken();
}
break;
case TerminalTokens.TokenNameDIVIDE:
// HTML tag closing
if (this.htmlTagsPtr == -1) return false;
htmlTag = this.htmlTags[this.htmlTagsPtr];
if ((token = readTokenAndConsume()) != TerminalTokens.TokenNameIdentifier) {
// not a closing html tag
return false;
}
char[] identifier = this.scanner.getCurrentIdentifierSource();
htmlIndex = getHtmlTagIndex(identifier);
if (htmlIndex == JAVADOC_TAGS_ID_MASK) return false;
int ptr = this.htmlTagsPtr;
while (!CharOperation.equals(htmlTag, identifier, false)) {
if (this.htmlTagsPtr <= 0) {
// consider the closing tag as invalid
this.htmlTagsPtr = ptr;
return false;
}
this.htmlTagsPtr--;
htmlTag = this.htmlTags[this.htmlTagsPtr];
}
// set closing flag
htmlIndex |= JAVADOC_CLOSED_TAG;
closing = true;
currentPosition = this.scanner.currentPosition;
break;
default:
return false;
}
// Looking for tag closing
switch (readTokenAndConsume()) {
case TerminalTokens.TokenNameLESS:
case TerminalTokens.TokenNameLESS_EQUAL:
// consider that the closing '>' is missing
return false;
case TerminalTokens.TokenNameGREATER:
// simple tag without attributes
break;
case TerminalTokens.TokenNameGREATER_EQUAL:
case TerminalTokens.TokenNameRIGHT_SHIFT:
case TerminalTokens.TokenNameRIGHT_SHIFT_EQUAL:
// simple tag without attributes, but the closing '>' is followed by an '=' or '>'
break;
default:
this.index = currentPosition;
loop: while (true) {
// currentPosition = this.index;
switch (readChar()) {
case '<':
if (hasWhitespaces) {
// not 100% sure this is a tag definition => give up
return false;
}
// opening tag => consider the current one as closed
this.index = currentPosition;
this.scanner.startPosition = currentPosition;
this.scanner.currentPosition = currentPosition;
this.scanner.currentCharacter = '<';
break loop;
case '>':
// simple tag without attributes
this.scanner.startPosition = this.index;
this.scanner.currentPosition = this.index;
this.scanner.currentCharacter = peekChar();
break loop;
default:
break;
}
if (this.index >= this.javadocTextEnd) {
// the end of the comment is reached => consider current tag as closed
this.index = currentPosition;
this.scanner.startPosition = currentPosition;
this.scanner.currentPosition = currentPosition;
break;
}
}
}
// Push texts
if (this.lineStarted && this.textStart != -1 && this.textStart < endTextPosition) {
pushText(this.textStart, endTextPosition, -1, htmlPtr);
}
pushText(previousPosition, this.index, htmlIndex, this.htmlTagsPtr);
this.textStart = -1;
valid = true;
}
finally {
if (valid) {
if (closing) {
this.htmlTagsPtr--;
}
} else if (!this.abort) {
if (incremented) {
this.htmlTagsPtr--;
if (this.htmlTagsPtr == -1) this.htmlTags = null;
}
this.scanner.resetTo(start, this.scanner.eofPosition-1);
this.index = start;
}
}
return valid;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#parseIdentifierTag(boolean)
*/
protected boolean parseIdentifierTag(boolean report) {
if (super.parseIdentifierTag(report)) {
createTag();
this.index = this.tagSourceEnd+1;
this.scanner.resetTo(this.index, this.javadocEnd);
return true;
}
this.tagValue = TAG_OTHERS_VALUE; // tag is invalid, do not keep the parsed tag value
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#parseParam()
*/
protected boolean parseParam() throws InvalidInputException {
boolean valid = super.parseParam();
if (!valid) {
this.scanner.resetTo(this.tagSourceEnd+1, this.javadocEnd);
this.index = this.tagSourceEnd+1;
char ch = peekChar();
// Try to push an identifier in the stack, otherwise restart from the end tag position
if (ch == ' ' || ScannerHelper.isWhitespace(ch)) {
int token = this.scanner.getNextToken();
if (token == TerminalTokens.TokenNameIdentifier) {
ch = peekChar();
if (ch == ' ' || ScannerHelper.isWhitespace(ch)) {
pushIdentifier(true, false);
pushParamName(false);
this.index = this.scanner.currentPosition;
valid = true;
}
}
this.scanner.resetTo(this.tagSourceEnd+1, this.javadocEnd);
}
this.tagValue = TAG_OTHERS_VALUE; // tag is invalid, do not keep the parsed tag value
}
return valid;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#parseReference()
*/
protected boolean parseReference() throws InvalidInputException {
boolean valid = super.parseReference();
if (!valid) {
this.scanner.resetTo(this.tagSourceEnd+1, this.javadocEnd);
this.index = this.tagSourceEnd+1;
this.tagValue = TAG_OTHERS_VALUE; // tag is invalid, do not keep the parsed tag value
}
return valid;
}
/*
* Parse @return tag declaration
*/
protected boolean parseReturn() {
createTag();
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#parseTag(int)
*/
protected boolean parseTag(int previousPosition) throws InvalidInputException {
// Do not parse javadoc tag inside <pre>...</pre> tags
if (this.htmlTagsPtr >= 0) {
int ptr = this.htmlTagsPtr;
while (ptr >= 0) {
if (getHtmlTagIndex(this.htmlTags[ptr--]) == JAVADOC_CODE_TAGS_ID) {
if (this.textStart == -1) this.textStart = this.inlineTagStarted ? this.inlineTagStart : previousPosition;
this.inlineTagStarted = false;
return true;
}
}
}
// Read tag name
int ptr = this.astPtr;
this.tagSourceStart = previousPosition;
this.scanner.startPosition = this.index;
this.scanner.currentCharacter = readChar();
switch (this.scanner.currentCharacter) {
case ' ':
case '*':
case '}':
// tag name is empty
this.tagSourceEnd = previousPosition;
if (this.textStart == -1) this.textStart = previousPosition;
return true;
default:
if (ScannerHelper.isWhitespace(this.scanner.currentCharacter)) {
// tag name is empty
this.tagSourceEnd = previousPosition;
if (this.textStart == -1) this.textStart = previousPosition;
return true;
}
break;
}
int currentPosition = this.index;
char currentChar = this.scanner.currentCharacter;
while (currentChar != ' ' && currentChar != '*' && currentChar != '}' && !ScannerHelper.isWhitespace(currentChar)) {
currentPosition = this.index;
currentChar = readChar();
}
this.tagSourceEnd = currentPosition - 1;
this.scanner.currentCharacter = currentChar;
this.scanner.currentPosition = currentPosition;
char[] tagName = this.scanner.getCurrentIdentifierSource();
int length = tagName.length;
this.index = this.tagSourceEnd+1;
// Decide which parse to perform depending on tag name
this.tagValue = TAG_OTHERS_VALUE;
boolean valid = false;
switch (tagName[0]) {
case 'a':
if (length == TAG_AUTHOR_LENGTH && CharOperation.equals(TAG_AUTHOR, tagName)) {
this.tagValue = TAG_AUTHOR_VALUE;
}
break;
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 && this.inlineTagStarted && CharOperation.equals(TAG_CODE, tagName)) {
this.tagValue = TAG_CODE_VALUE;
}
break;
case 'd':
if (length == TAG_DEPRECATED_LENGTH && CharOperation.equals(TAG_DEPRECATED, tagName)) {
this.deprecated = true;
valid = true;
this.tagValue = TAG_DEPRECATED_VALUE;
} else if (length == TAG_DOC_ROOT_LENGTH && CharOperation.equals(TAG_DOC_ROOT, tagName)) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=227730
// identify @docRoot tag as a base tag that does not expect any argument
valid = true;
this.tagValue = TAG_DOC_ROOT_VALUE;
}
break;
case 'e':
if (length == TAG_EXCEPTION_LENGTH && CharOperation.equals(TAG_EXCEPTION, tagName)) {
this.tagValue = TAG_EXCEPTION_VALUE;
valid = parseThrows();
}
break;
case 'i':
if (length == TAG_INHERITDOC_LENGTH && CharOperation.equals(TAG_INHERITDOC, tagName)) {
if (this.reportProblems) {
recordInheritedPosition((((long) this.tagSourceStart) << 32) + this.tagSourceEnd);
}
valid = true;
this.tagValue = TAG_INHERITDOC_VALUE;
}
break;
case 'l':
if (length == TAG_LINK_LENGTH && CharOperation.equals(TAG_LINK, tagName)) {
this.tagValue = TAG_LINK_VALUE;
if (this.inlineTagStarted || (this.kind & COMPLETION_PARSER) != 0) {
valid = parseReference();
} else {
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
// Cannot have @link outside inline comment
valid = false;
if (this.reportProblems) {
this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
}
}
} else if (length == TAG_LINKPLAIN_LENGTH && CharOperation.equals(TAG_LINKPLAIN, tagName)) {
this.tagValue = TAG_LINKPLAIN_VALUE;
if (this.inlineTagStarted) {
valid = parseReference();
} else {
valid = false;
if (this.reportProblems) {
this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
}
}
} else if (length == TAG_LITERAL_LENGTH && this.inlineTagStarted && CharOperation.equals(TAG_LITERAL, tagName)) {
this.tagValue = TAG_LITERAL_VALUE;
}
break;
case 'p':
if (length == TAG_PARAM_LENGTH && CharOperation.equals(TAG_PARAM, tagName)) {
this.tagValue = TAG_PARAM_VALUE;
valid = parseParam();
}
break;
case 's':
if (length == TAG_SEE_LENGTH && CharOperation.equals(TAG_SEE, tagName)) {
if (this.inlineTagStarted) {
// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
// Cannot have @see inside inline comment
valid = false;
if (this.reportProblems) {
this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
}
} else {
this.tagValue = TAG_SEE_VALUE;
valid = parseReference();
}
} else if (length == TAG_SERIAL_LENGTH && CharOperation.equals(TAG_SERIAL, tagName)) {
this.tagValue = TAG_SERIAL_VALUE;
} else if (length == TAG_SERIAL_DATA_LENGTH && CharOperation.equals(TAG_SERIAL_DATA, tagName)) {
this.tagValue = TAG_SERIAL_DATA_VALUE;
} else if (length == TAG_SERIAL_FIELD_LENGTH && CharOperation.equals(TAG_SERIAL_FIELD, tagName)) {
this.tagValue = TAG_SERIAL_FIELD_VALUE;
} else if (length == TAG_SINCE_LENGTH && CharOperation.equals(TAG_SINCE, tagName)) {
this.tagValue = TAG_SINCE_VALUE;
}
break;
case 'v':
if (length == TAG_VALUE_LENGTH && CharOperation.equals(TAG_VALUE, tagName)) {
this.tagValue = TAG_VALUE_VALUE;
if (this.sourceLevel >= ClassFileConstants.JDK1_5) {
if (this.inlineTagStarted) {
valid = parseReference();
} else {
valid = false;
if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
}
}
} else if (length == TAG_VERSION_LENGTH && CharOperation.equals(TAG_VERSION, tagName)) {
this.tagValue = TAG_VERSION_VALUE;
} else {
createTag();
}
break;
case 'r':
if (length == TAG_RETURN_LENGTH && CharOperation.equals(TAG_RETURN, tagName)) {
this.tagValue = TAG_RETURN_VALUE;
valid = parseReturn();
}
break;
case 't':
if (length == TAG_THROWS_LENGTH && CharOperation.equals(TAG_THROWS, tagName)) {
this.tagValue = TAG_THROWS_VALUE;
valid = parseThrows();
}
break;
default:
createTag();
break;
}
consumeToken();
this.textStart = -1;
// the javadoc parser may not create tag for some valid tags: force tag creation for such tag.
if (valid) {
switch (this.tagValue) {
case TAG_INHERITDOC_VALUE:
case TAG_DEPRECATED_VALUE:
createTag();
break;
}
} else if (this.invalidTagName) {
this.textStart = previousPosition;
} else if (this.astPtr == ptr) {
createTag();
}
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#parseThrows()
*/
protected boolean parseThrows() {
boolean valid = super.parseThrows();
if (!valid) {
// If invalid, restart from the end tag position
this.scanner.resetTo(this.tagSourceEnd+1, this.javadocEnd);
this.index = this.tagSourceEnd+1;
this.tagValue = TAG_OTHERS_VALUE; // tag is invalid, do not keep the parsed tag value
}
return valid;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#pushParamName(boolean)
*/
protected boolean pushParamName(boolean isTypeParam) {
int lineTagStart = this.scanner.getLineNumber(this.tagSourceStart);
FormatJavadocBlock block = new FormatJavadocBlock(this.tagSourceStart, this.tagSourceEnd, lineTagStart, TAG_PARAM_VALUE);
int start = (int) (this.identifierPositionStack[0] >>> 32);
int lineStart = this.scanner.getLineNumber(start);
FormatJavadocReference reference;
reference = new FormatJavadocReference(start, (int) this.identifierPositionStack[isTypeParam ? 2 : 0], lineStart);
block.reference = reference;
block.sourceEnd = reference.sourceEnd;
pushOnAstStack(block, true);
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.JavadocParser#pushSeeRef(java.lang.Object)
*/
protected boolean pushSeeRef(Object statement) {
FormatJavadocReference reference = (FormatJavadocReference) statement;
int lineTagStart = this.scanner.getLineNumber(this.tagSourceStart);
FormatJavadocBlock block = new FormatJavadocBlock(this.tagSourceStart, this.tagSourceEnd, lineTagStart, this.tagValue);
block.reference = reference;
block.sourceEnd = reference.sourceEnd;
if (this.inlineTagStarted) {
block.sourceStart = this.inlineTagStart;
FormatJavadocBlock previousBlock = null;
if (this.astPtr == -1) {
int lineStart = this.scanner.getLineNumber(this.inlineTagStart);
previousBlock = new FormatJavadocBlock(this.inlineTagStart, this.tagSourceEnd, lineStart, NO_TAG_VALUE);
previousBlock.sourceEnd = reference.sourceEnd;
pushOnAstStack(previousBlock, true);
} else {
previousBlock = (FormatJavadocBlock) this.astStack[this.astPtr];
}
previousBlock.addBlock(block, this.htmlTagsPtr == -1 ? 0 : this.htmlTagsPtr);
block.flags |= FormatJavadocBlock.INLINED;
} else {
pushOnAstStack(block, true);
}
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#pushText(int, int)
*/
protected void pushText(int start, int end) {
pushText(start, end, -1, this.htmlTagsPtr == -1 ? 0 : this.htmlTagsPtr);
}
private void pushText(int start, int end, int htmlIndex, int htmlDepth) {
// Search previous tag on which to add the text element
FormatJavadocBlock previousBlock = null;
int previousStart = start;
int lineStart = this.scanner.getLineNumber(start);
if (this.astPtr == -1) {
previousBlock = new FormatJavadocBlock(start, start, lineStart, NO_TAG_VALUE);
pushOnAstStack(previousBlock, true);
} else {
previousBlock = (FormatJavadocBlock) this.astStack[this.astPtr];
previousStart = previousBlock.sourceStart;
}
// If we're in a inline tag, then retrieve previous tag in its fragments
if (this.inlineTagStarted) {
if (previousBlock.nodes == null) {
// no existing fragment => just add the element
} else {
// If last fragment is a tag, then use it as previous tag
FormatJavadocNode lastNode = previousBlock.nodes[previousBlock.nodesPtr];
while (lastNode != null && lastNode.isText()) {
lastNode = lastNode.getLastNode();
}
if (lastNode != null) {
previousBlock = (FormatJavadocBlock) lastNode;
previousStart = previousBlock.sourceStart;
}
}
}
// Add the text
FormatJavadocText text = new FormatJavadocText(start, end-1, lineStart, htmlIndex, htmlDepth==-1 ? 0 : htmlDepth);
previousBlock.addText(text);
previousBlock.sourceStart = previousStart;
if (lineStart == previousBlock.lineStart) {
previousBlock.flags |= FormatJavadocBlock.TEXT_ON_TAG_LINE;
}
this.textStart = -1;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#pushThrowName(java.lang.Object)
*/
protected boolean pushThrowName(Object typeRef) {
int lineStart = this.scanner.getLineNumber(this.tagSourceStart);
FormatJavadocBlock block = new FormatJavadocBlock(this.tagSourceStart, this.tagSourceEnd, lineStart, this.tagValue);
block.reference = (FormatJavadocReference) typeRef;
block.sourceEnd = block.reference.sourceEnd;
pushOnAstStack(block, true);
return true;
}
/*
* (non-Javadoc)
* Will update the inline tag position (end position) once tag was fully parsed.
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#refreshInlineTagPosition(int)
*/
protected void refreshInlineTagPosition(int previousPosition) {
if (this.astPtr != -1) {
FormatJavadocNode previousBlock = (FormatJavadocNode) this.astStack[this.astPtr];
if (this.inlineTagStarted) {
FormatJavadocNode lastNode = previousBlock;
while (lastNode != null) {
lastNode.sourceEnd = previousPosition;
lastNode = lastNode.getLastNode();
}
}
}
}
/*
* Store the html tags level when entering an inline tag in case a wrong sequence
* of opening/closing tags is defined inside it. Then, when leaving the inline tag
* the level is reset to the entering value and avoid to wrongly attach subsequent
* html tags to node inside the inline tag last node...
*/
protected void setInlineTagStarted(boolean started) {
super.setInlineTagStarted(started);
if (started) {
this.inlineHtmlTagsPtr = this.htmlTagsPtr;
} else {
if (this.htmlTagsPtr > this.inlineHtmlTagsPtr) {
this.htmlTagsPtr = this.inlineHtmlTagsPtr;
}
}
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("FormatterCommentParser\n"); //$NON-NLS-1$
buffer.append(super.toString());
return buffer.toString();
}
public String toDebugString() {
if (this.docComment == null) {
return "No javadoc!"; //$NON-NLS-1$
}
return ((FormatJavadoc)this.docComment).toDebugString(this.source);
}
/*
* Add stored tag elements to associated comment.
* Clean all blocks (i.e. resize arrays to avoid null slots)
* Set extra information on block about line relative positions.
*/
protected void updateDocComment() {
int length = this.astPtr + 1;
FormatJavadoc formatJavadoc = new FormatJavadoc(this.javadocStart, this.javadocEnd, length);
if (length > 0) {
formatJavadoc.blocks = new FormatJavadocBlock[length];
for (int i=0; i<length; i++) {
FormatJavadocBlock block = (FormatJavadocBlock) this.astStack[i];
block.clean();
block.update(this.scanner);
formatJavadoc.blocks[i] = block;
if (i== 0) {
block.flags |= FormatJavadocBlock.FIRST;
}
}
}
formatJavadoc.textStart = this.javadocTextStart;
formatJavadoc.textEnd = this.javadocTextEnd;
formatJavadoc.lineStart = this.scanner.getLineNumber(this.javadocTextStart);
formatJavadoc.lineEnd = this.scanner.getLineNumber(this.javadocTextEnd);
FormatJavadocBlock firstBlock = formatJavadoc.getFirstBlock();
if (firstBlock != null) {
firstBlock.setHeaderLine(formatJavadoc.lineStart);
}
this.docComment = formatJavadoc;
if (DefaultCodeFormatter.DEBUG) {
System.out.println(toDebugString());
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#verifyEndLine(int)
*/
protected boolean verifyEndLine(int textPosition) {
// do not verify anything at end of line while formatting
return true;
}
protected boolean verifySpaceOrEndComment() {
// Don't care if there's no spaces after a reference...
return true;
}
}