/******************************************************************************* * 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; } }