org.eclipse.jdt.core.dom.DocCommentParser.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.core.dom.DocCommentParser.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.dom;

import java.util.Iterator;
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.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() {
        StringBuffer buffer = new StringBuffer();
        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() {
        TagElement tagElement = this.ast.newTagElement();
        int position = this.scanner.currentPosition;
        this.scanner.resetTo(this.tagSourceStart, this.tagSourceEnd);
        StringBuffer tagName = new StringBuffer();
        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);
    }

    @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;
    }

    @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();
                    }
                } 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();
                    } 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 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]);
        }
    }
}