org.eclipse.jdt.internal.compiler.ast.Javadoc.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.compiler.ast.Javadoc.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 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
 *     Stephan Herrmann - Contribution for
 *                        Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec)
 *                        Bug 429958 - [1.8][null] evaluate new DefaultLocation attribute of @NonNullByDefault
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants;

/**
 * Node representing a structured Javadoc comment
 */
public class Javadoc extends ASTNode {

    public JavadocSingleNameReference[] paramReferences; // @param
    public JavadocSingleTypeReference[] paramTypeParameters; // @param
    public TypeReference[] exceptionReferences; // @throws, @exception
    public JavadocReturnStatement returnStatement; // @return
    public Expression[] seeReferences; // @see
    public IJavadocTypeReference[] usesReferences; // @uses
    public IJavadocTypeReference[] providesReferences; // @provides
    public long[] inheritedPositions = null;
    // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
    // Store param references for tag with invalid syntax
    public JavadocSingleNameReference[] invalidParameters; // @param
    // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=153399
    // Store value tag positions
    public long valuePositions = -1;

    public Javadoc(int sourceStart, int sourceEnd) {
        this.sourceStart = sourceStart;
        this.sourceEnd = sourceEnd;
        this.bits |= ASTNode.ResolveJavadoc;
    }

    /**
     * Returns whether a type can be seen at a given visibility level or not.
     *
     * @param visibility Level of visiblity allowed to see references
     * @param modifiers modifiers of java element to be seen
     * @return true if the type can be seen, false otherwise
     */
    boolean canBeSeen(int visibility, int modifiers) {
        if (modifiers < 0)
            return true;
        switch (modifiers & ExtraCompilerModifiers.AccVisibilityMASK) {
        case ClassFileConstants.AccPublic:
            return true;
        case ClassFileConstants.AccProtected:
            return (visibility != ClassFileConstants.AccPublic);
        case ClassFileConstants.AccDefault:
            return (visibility == ClassFileConstants.AccDefault || visibility == ClassFileConstants.AccPrivate);
        case ClassFileConstants.AccPrivate:
            return (visibility == ClassFileConstants.AccPrivate);
        }
        return true;
    }

    /*
     * Search node with a given staring position in javadoc objects arrays.
     */
    public ASTNode getNodeStartingAt(int start) {
        int length = 0;
        // parameters array
        if (this.paramReferences != null) {
            length = this.paramReferences.length;
            for (int i = 0; i < length; i++) {
                JavadocSingleNameReference param = this.paramReferences[i];
                if (param.sourceStart == start) {
                    return param;
                }
            }
        }
        // array of invalid syntax tags parameters
        if (this.invalidParameters != null) {
            length = this.invalidParameters.length;
            for (int i = 0; i < length; i++) {
                JavadocSingleNameReference param = this.invalidParameters[i];
                if (param.sourceStart == start) {
                    return param;
                }
            }
        }
        // type parameters array
        if (this.paramTypeParameters != null) {
            length = this.paramTypeParameters.length;
            for (int i = 0; i < length; i++) {
                JavadocSingleTypeReference param = this.paramTypeParameters[i];
                if (param.sourceStart == start) {
                    return param;
                }
            }
        }
        // thrown exception array
        if (this.exceptionReferences != null) {
            length = this.exceptionReferences.length;
            for (int i = 0; i < length; i++) {
                TypeReference typeRef = this.exceptionReferences[i];
                if (typeRef.sourceStart == start) {
                    return typeRef;
                }
            }
        }
        // references array
        if (this.seeReferences != null) {
            length = this.seeReferences.length;
            for (int i = 0; i < length; i++) {
                org.eclipse.jdt.internal.compiler.ast.Expression expression = this.seeReferences[i];
                if (expression.sourceStart == start) {
                    return expression;
                } else if (expression instanceof JavadocAllocationExpression) {
                    JavadocAllocationExpression allocationExpr = (JavadocAllocationExpression) this.seeReferences[i];
                    // if binding is valid then look at arguments
                    if (allocationExpr.binding != null && allocationExpr.binding.isValidBinding()) {
                        if (allocationExpr.arguments != null) {
                            for (int j = 0, l = allocationExpr.arguments.length; j < l; j++) {
                                if (allocationExpr.arguments[j].sourceStart == start) {
                                    return allocationExpr.arguments[j];
                                }
                            }
                        }
                    }
                } else if (expression instanceof JavadocMessageSend) {
                    JavadocMessageSend messageSend = (JavadocMessageSend) this.seeReferences[i];
                    // if binding is valid then look at arguments
                    if (messageSend.binding != null && messageSend.binding.isValidBinding()) {
                        if (messageSend.arguments != null) {
                            for (int j = 0, l = messageSend.arguments.length; j < l; j++) {
                                if (messageSend.arguments[j].sourceStart == start) {
                                    return messageSend.arguments[j];
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /*
     * @see org.eclipse.jdt.internal.compiler.ast.ASTNode#print(int, java.lang.StringBuffer)
     */
    @Override
    public StringBuffer print(int indent, StringBuffer output) {
        printIndent(indent, output).append("/**\n"); //$NON-NLS-1$
        if (this.paramReferences != null) {
            for (int i = 0, length = this.paramReferences.length; i < length; i++) {
                printIndent(indent + 1, output).append(" * @param "); //$NON-NLS-1$
                this.paramReferences[i].print(indent, output).append('\n');
            }
        }
        if (this.paramTypeParameters != null) {
            for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) {
                printIndent(indent + 1, output).append(" * @param <"); //$NON-NLS-1$
                this.paramTypeParameters[i].print(indent, output).append(">\n"); //$NON-NLS-1$
            }
        }
        if (this.returnStatement != null) {
            printIndent(indent + 1, output).append(" * @"); //$NON-NLS-1$
            this.returnStatement.print(indent, output).append('\n');
        }
        if (this.exceptionReferences != null) {
            for (int i = 0, length = this.exceptionReferences.length; i < length; i++) {
                printIndent(indent + 1, output).append(" * @throws "); //$NON-NLS-1$
                this.exceptionReferences[i].print(indent, output).append('\n');
            }
        }
        if (this.seeReferences != null) {
            for (int i = 0, length = this.seeReferences.length; i < length; i++) {
                printIndent(indent + 1, output).append(" * @see "); //$NON-NLS-1$
                this.seeReferences[i].print(indent, output).append('\n');
            }
        }
        printIndent(indent, output).append(" */\n"); //$NON-NLS-1$
        return output;
    }

    /*
     * Resolve type javadoc
     */
    public void resolve(ClassScope scope) {
        if ((this.bits & ASTNode.ResolveJavadoc) == 0) {
            return;
        }

        this.bits &= ~ASTNode.ResolveJavadoc;// avoid double resolution

        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=247037, @inheritDoc tag cannot
        // be used in the documentation comment for a class or interface.
        if (this.inheritedPositions != null) {
            int length = this.inheritedPositions.length;
            for (int i = 0; i < length; ++i) {
                int start = (int) (this.inheritedPositions[i] >>> 32);
                int end = (int) this.inheritedPositions[i];
                scope.problemReporter().javadocUnexpectedTag(start, end);
            }
        }
        // @param tags
        int paramTagsSize = this.paramReferences == null ? 0 : this.paramReferences.length;
        for (int i = 0; i < paramTagsSize; i++) {
            JavadocSingleNameReference param = this.paramReferences[i];
            scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd);
        }
        resolveTypeParameterTags(scope, true);

        // @return tags
        if (this.returnStatement != null) {
            scope.problemReporter().javadocUnexpectedTag(this.returnStatement.sourceStart,
                    this.returnStatement.sourceEnd);
        }

        // @throws/@exception tags
        int throwsTagsLength = this.exceptionReferences == null ? 0 : this.exceptionReferences.length;
        for (int i = 0; i < throwsTagsLength; i++) {
            TypeReference typeRef = this.exceptionReferences[i];
            int start, end;
            if (typeRef instanceof JavadocSingleTypeReference) {
                JavadocSingleTypeReference singleRef = (JavadocSingleTypeReference) typeRef;
                start = singleRef.tagSourceStart;
                end = singleRef.tagSourceEnd;
            } else if (typeRef instanceof JavadocQualifiedTypeReference) {
                JavadocQualifiedTypeReference qualifiedRef = (JavadocQualifiedTypeReference) typeRef;
                start = qualifiedRef.tagSourceStart;
                end = qualifiedRef.tagSourceEnd;
            } else {
                start = typeRef.sourceStart;
                end = typeRef.sourceEnd;
            }
            scope.problemReporter().javadocUnexpectedTag(start, end);
        }

        // @see tags
        int seeTagsLength = this.seeReferences == null ? 0 : this.seeReferences.length;
        for (int i = 0; i < seeTagsLength; i++) {
            resolveReference(this.seeReferences[i], scope);
        }

        // @value tag
        boolean source15 = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
        if (!source15 && this.valuePositions != -1) {
            scope.problemReporter().javadocUnexpectedTag((int) (this.valuePositions >>> 32),
                    (int) this.valuePositions);
        }
    }

    /*
     * Resolve compilation unit javadoc
     */
    public void resolve(CompilationUnitScope unitScope) {
        if ((this.bits & ASTNode.ResolveJavadoc) == 0) {
            return;
        }
        // Do nothing - This is to mimic the SDK's javadoc tool behavior, which neither
        // sanity checks nor generates documentation using comments at the CU scope
        // (unless the unit happens to be package-info.java - in which case we don't come here.)
    }

    /*
     * Resolve method javadoc
     */
    public void resolve(MethodScope methScope) {
        if ((this.bits & ASTNode.ResolveJavadoc) == 0) {
            return;
        }

        this.bits &= ~ASTNode.ResolveJavadoc;// avoid double resolution

        // get method declaration
        AbstractMethodDeclaration methDecl = methScope.referenceMethod();
        boolean overriding = methDecl == null
                /* field declaration */ || methDecl.binding == null /* compiler error */
                        ? false
                        : !methDecl.binding.isStatic()
                                && ((methDecl.binding.modifiers & (ExtraCompilerModifiers.AccImplementing
                                        | ExtraCompilerModifiers.AccOverriding)) != 0);

        // @see tags
        int seeTagsLength = this.seeReferences == null ? 0 : this.seeReferences.length;
        boolean superRef = false;
        for (int i = 0; i < seeTagsLength; i++) {

            // Resolve reference
            resolveReference(this.seeReferences[i], methScope);

            // see whether we can have a super reference
            if (methDecl != null && !superRef) {
                if (!methDecl.isConstructor()) {
                    if (overriding && this.seeReferences[i] instanceof JavadocMessageSend) {
                        JavadocMessageSend messageSend = (JavadocMessageSend) this.seeReferences[i];
                        // if binding is valid then look if we have a reference to an overriden method/constructor
                        if (messageSend.binding != null && messageSend.binding.isValidBinding()
                                && messageSend.actualReceiverType instanceof ReferenceBinding) {
                            ReferenceBinding methodReceiverType = (ReferenceBinding) messageSend.actualReceiverType;
                            TypeBinding superType = methDecl.binding.declaringClass
                                    .findSuperTypeOriginatingFrom(methodReceiverType);
                            if (superType != null
                                    && TypeBinding.notEquals(superType.original(), methDecl.binding.declaringClass)
                                    && CharOperation.equals(messageSend.selector, methDecl.selector)) {
                                if (methScope.environment().methodVerifier().doesMethodOverride(methDecl.binding,
                                        messageSend.binding.original())) {
                                    superRef = true;
                                }
                            }
                        }
                    }
                } else if (this.seeReferences[i] instanceof JavadocAllocationExpression) {
                    JavadocAllocationExpression allocationExpr = (JavadocAllocationExpression) this.seeReferences[i];
                    // if binding is valid then look if we have a reference to an overriden method/constructor
                    if (allocationExpr.binding != null && allocationExpr.binding.isValidBinding()) {
                        ReferenceBinding allocType = (ReferenceBinding) allocationExpr.resolvedType.original();
                        ReferenceBinding superType = (ReferenceBinding) methDecl.binding.declaringClass
                                .findSuperTypeOriginatingFrom(allocType);
                        if (superType != null
                                && TypeBinding.notEquals(superType.original(), methDecl.binding.declaringClass)) {
                            MethodBinding superConstructor = methScope.getConstructor(superType,
                                    methDecl.binding.parameters, allocationExpr);
                            if (superConstructor.isValidBinding()
                                    && superConstructor.original() == allocationExpr.binding.original()) {
                                MethodBinding current = methDecl.binding;
                                // work 'against' better inference in 1.8 (otherwise comparing (G<T> with G<Object>) would fail):
                                if (methScope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8
                                        && current.typeVariables != Binding.NO_TYPE_VARIABLES) {
                                    current = current.asRawMethod(methScope.environment());
                                }
                                if (superConstructor.areParametersEqual(current)) {
                                    superRef = true;
                                }
                            }
                        }
                    }
                }
            }
        }

        // Look at @Override annotations
        if (!superRef && methDecl != null && methDecl.annotations != null) {
            int length = methDecl.annotations.length;
            for (int i = 0; i < length && !superRef; i++) {
                superRef = (methDecl.binding.tagBits & TagBits.AnnotationOverride) != 0;
            }
        }

        // Store if a reference exists to an overriden method/constructor or the method is in a local type,
        boolean reportMissing = methDecl == null || !((overriding && this.inheritedPositions != null) || superRef
                || (methDecl.binding.declaringClass != null && methDecl.binding.declaringClass.isLocalType()));
        if (!overriding && this.inheritedPositions != null) {
            int length = this.inheritedPositions.length;
            for (int i = 0; i < length; ++i) {
                int start = (int) (this.inheritedPositions[i] >>> 32);
                int end = (int) this.inheritedPositions[i];
                methScope.problemReporter().javadocUnexpectedTag(start, end);
            }
        }

        // @param tags
        CompilerOptions compilerOptions = methScope.compilerOptions();
        resolveParamTags(methScope, reportMissing,
                compilerOptions.reportUnusedParameterIncludeDocCommentReference /* considerParamRefAsUsage*/);
        resolveTypeParameterTags(methScope,
                reportMissing && compilerOptions.reportMissingJavadocTagsMethodTypeParameters);

        // @return tags
        if (this.returnStatement == null) {
            if (reportMissing && methDecl != null) {
                if (methDecl.isMethod()) {
                    MethodDeclaration meth = (MethodDeclaration) methDecl;
                    if (meth.binding.returnType != TypeBinding.VOID) {
                        // method with return should have @return tag
                        methScope.problemReporter().javadocMissingReturnTag(meth.returnType.sourceStart,
                                meth.returnType.sourceEnd, methDecl.binding.modifiers);
                    }
                }
            }
        } else {
            this.returnStatement.resolve(methScope);
        }

        // @throws/@exception tags
        resolveThrowsTags(methScope, reportMissing);

        // @value tag
        boolean source15 = compilerOptions.sourceLevel >= ClassFileConstants.JDK1_5;
        if (!source15 && methDecl != null && this.valuePositions != -1) {
            methScope.problemReporter().javadocUnexpectedTag((int) (this.valuePositions >>> 32),
                    (int) this.valuePositions);
        }

        // Resolve param tags with invalid syntax
        int length = this.invalidParameters == null ? 0 : this.invalidParameters.length;
        for (int i = 0; i < length; i++) {
            this.invalidParameters[i].resolve(methScope, false, false);
        }

        if (methScope.isModuleScope()) {
            resolveUsesTags(methScope, reportMissing);
            resolveProvidesTags(methScope, reportMissing);
        }
    }

    private void resolveReference(Expression reference, Scope scope) {

        // Perform resolve
        int problemCount = scope.referenceContext().compilationResult().problemCount;
        switch (scope.kind) {
        case Scope.METHOD_SCOPE:
            reference.resolveType((MethodScope) scope);
            break;
        case Scope.CLASS_SCOPE:
            reference.resolveType((ClassScope) scope);
            break;
        }
        boolean hasProblems = scope.referenceContext().compilationResult().problemCount > problemCount;

        // Verify field references
        boolean source15 = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
        int scopeModifiers = -1;
        if (reference instanceof JavadocFieldReference) {
            JavadocFieldReference fieldRef = (JavadocFieldReference) reference;

            // Verify if this is a method reference
            // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51911
            if (fieldRef.methodBinding != null) {
                // cannot refer to method for @value tag
                if (fieldRef.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) {
                    if (scopeModifiers == -1)
                        scopeModifiers = scope.getDeclarationModifiers();
                    scope.problemReporter().javadocInvalidValueReference(fieldRef.sourceStart, fieldRef.sourceEnd,
                            scopeModifiers);
                } else if (fieldRef.actualReceiverType != null) {
                    if (scope.enclosingSourceType().isCompatibleWith(fieldRef.actualReceiverType)) {
                        fieldRef.bits |= ASTNode.SuperAccess;
                    }
                    ReferenceBinding resolvedType = (ReferenceBinding) fieldRef.actualReceiverType;
                    if (CharOperation.equals(resolvedType.sourceName(), fieldRef.token)) {
                        fieldRef.methodBinding = scope.getConstructor(resolvedType, Binding.NO_TYPES, fieldRef);
                    } else {
                        fieldRef.methodBinding = scope.findMethod(resolvedType, fieldRef.token, Binding.NO_TYPES,
                                fieldRef, false);
                    }
                }
            }

            // Verify whether field ref should be static or not (for @value tags)
            else if (source15 && fieldRef.binding != null && fieldRef.binding.isValidBinding()) {
                if (fieldRef.tagValue == JavadocTagConstants.TAG_VALUE_VALUE && !fieldRef.binding.isStatic()) {
                    if (scopeModifiers == -1)
                        scopeModifiers = scope.getDeclarationModifiers();
                    scope.problemReporter().javadocInvalidValueReference(fieldRef.sourceStart, fieldRef.sourceEnd,
                            scopeModifiers);
                }
            }

            // Verify type references
            if (!hasProblems && fieldRef.binding != null && fieldRef.binding.isValidBinding()
                    && fieldRef.actualReceiverType instanceof ReferenceBinding) {
                ReferenceBinding resolvedType = (ReferenceBinding) fieldRef.actualReceiverType;
                verifyTypeReference(fieldRef, fieldRef.receiver, scope, source15, resolvedType,
                        fieldRef.binding.modifiers);
            }

            // That's it for field references
            return;
        }

        // Verify type references
        if (!hasProblems
                && (reference instanceof JavadocSingleTypeReference
                        || reference instanceof JavadocQualifiedTypeReference)
                && reference.resolvedType instanceof ReferenceBinding) {
            ReferenceBinding resolvedType = (ReferenceBinding) reference.resolvedType;
            verifyTypeReference(reference, reference, scope, source15, resolvedType, resolvedType.modifiers);
        }

        // Verify that message reference are not used for @value tags
        if (reference instanceof JavadocMessageSend) {
            JavadocMessageSend msgSend = (JavadocMessageSend) reference;

            // tag value
            if (source15 && msgSend.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) { // cannot refer to method for @value tag
                if (scopeModifiers == -1)
                    scopeModifiers = scope.getDeclarationModifiers();
                scope.problemReporter().javadocInvalidValueReference(msgSend.sourceStart, msgSend.sourceEnd,
                        scopeModifiers);
            }

            // Verify type references
            if (!hasProblems && msgSend.binding != null && msgSend.binding.isValidBinding()
                    && msgSend.actualReceiverType instanceof ReferenceBinding) {
                ReferenceBinding resolvedType = (ReferenceBinding) msgSend.actualReceiverType;
                verifyTypeReference(msgSend, msgSend.receiver, scope, source15, resolvedType,
                        msgSend.binding.modifiers);
            }
        }

        // Verify that constructor reference are not used for @value tags
        else if (reference instanceof JavadocAllocationExpression) {
            JavadocAllocationExpression alloc = (JavadocAllocationExpression) reference;

            // tag value
            if (source15 && alloc.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) { // cannot refer to method for @value tag
                if (scopeModifiers == -1)
                    scopeModifiers = scope.getDeclarationModifiers();
                scope.problemReporter().javadocInvalidValueReference(alloc.sourceStart, alloc.sourceEnd,
                        scopeModifiers);
            }

            // Verify type references
            if (!hasProblems && alloc.binding != null && alloc.binding.isValidBinding()
                    && alloc.resolvedType instanceof ReferenceBinding) {
                ReferenceBinding resolvedType = (ReferenceBinding) alloc.resolvedType;
                verifyTypeReference(alloc, alloc.type, scope, source15, resolvedType, alloc.binding.modifiers);
            }
        }

        // Verify that there's no type variable reference
        // (javadoc does not accept them and this is not a referenced bug or requested enhancement)
        else if (reference instanceof JavadocSingleTypeReference && reference.resolvedType != null
                && reference.resolvedType.isTypeVariable()) {
            scope.problemReporter().javadocInvalidReference(reference.sourceStart, reference.sourceEnd);
        }
    }

    /*
     * Resolve @param tags while method scope
     */
    private void resolveParamTags(MethodScope scope, boolean reportMissing, boolean considerParamRefAsUsage) {
        AbstractMethodDeclaration methodDecl = scope.referenceMethod();
        int paramTagsSize = this.paramReferences == null ? 0 : this.paramReferences.length;

        // If no referenced method (field initializer for example) then report a problem for each param tag
        if (methodDecl == null) {
            for (int i = 0; i < paramTagsSize; i++) {
                JavadocSingleNameReference param = this.paramReferences[i];
                scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd);
            }
            return;
        }

        // If no param tags then report a problem for each method argument
        int argumentsSize = methodDecl.arguments == null ? 0 : methodDecl.arguments.length;
        if (paramTagsSize == 0) {
            if (reportMissing) {
                for (int i = 0; i < argumentsSize; i++) {
                    Argument arg = methodDecl.arguments[i];
                    scope.problemReporter().javadocMissingParamTag(arg.name, arg.sourceStart, arg.sourceEnd,
                            methodDecl.binding.modifiers);
                }
            }
        } else {
            LocalVariableBinding[] bindings = new LocalVariableBinding[paramTagsSize];
            int maxBindings = 0;

            // Scan all @param tags
            for (int i = 0; i < paramTagsSize; i++) {
                JavadocSingleNameReference param = this.paramReferences[i];
                param.resolve(scope, true, considerParamRefAsUsage);
                if (param.binding != null && param.binding.isValidBinding()) {
                    // Verify duplicated tags
                    boolean found = false;
                    for (int j = 0; j < maxBindings && !found; j++) {
                        if (bindings[j] == param.binding) {
                            scope.problemReporter().javadocDuplicatedParamTag(param.token, param.sourceStart,
                                    param.sourceEnd, methodDecl.binding.modifiers);
                            found = true;
                        }
                    }
                    if (!found) {
                        bindings[maxBindings++] = (LocalVariableBinding) param.binding;
                    }
                }
            }

            // Look for undocumented arguments
            if (reportMissing) {
                for (int i = 0; i < argumentsSize; i++) {
                    Argument arg = methodDecl.arguments[i];
                    boolean found = false;
                    for (int j = 0; j < maxBindings && !found; j++) {
                        LocalVariableBinding binding = bindings[j];
                        if (arg.binding == binding) {
                            found = true;
                        }
                    }
                    if (!found) {
                        scope.problemReporter().javadocMissingParamTag(arg.name, arg.sourceStart, arg.sourceEnd,
                                methodDecl.binding.modifiers);
                    }
                }
            }
        }
    }

    /*
     * Resolve @uses tags while block scope
     */
    private void resolveUsesTags(BlockScope scope, boolean reportMissing) {
        ModuleDeclaration moduleDecl = (ModuleDeclaration) scope.referenceContext();
        int usesTagsSize = this.usesReferences == null ? 0 : this.usesReferences.length;

        // If no referenced module then report a problem for each uses tag
        if (moduleDecl == null) {
            for (int i = 0; i < usesTagsSize; i++) {
                IJavadocTypeReference uses = this.usesReferences[i];
                scope.problemReporter().javadocUnexpectedTag(uses.getTagSourceStart(), uses.getTagSourceEnd());
            }
            return;
        }

        // If no uses tags then report a problem for each uses reference
        int usesSize = moduleDecl.usesCount;
        if (usesTagsSize == 0) {
            if (reportMissing) {
                for (int i = 0; i < usesSize; i++) {
                    UsesStatement uses = moduleDecl.uses[i];
                    scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart,
                            uses.sourceEnd, moduleDecl.binding.modifiers);
                }
            }
        } else {
            TypeBinding[] bindings = new TypeBinding[usesTagsSize];
            int maxBindings = 0;

            // Scan all @uses tags
            for (int i = 0; i < usesTagsSize; i++) {
                TypeReference usesRef = (TypeReference) this.usesReferences[i];
                try {
                    usesRef.resolve(scope);
                    if (usesRef.resolvedType != null && usesRef.resolvedType.isValidBinding()) {
                        // Verify duplicated tags
                        boolean found = false;
                        for (int j = 0; j < maxBindings && !found; j++) {
                            if (bindings[j].equals(usesRef.resolvedType)) {
                                scope.problemReporter().javadocDuplicatedUsesTag(usesRef.sourceStart,
                                        usesRef.sourceEnd);
                                found = true;
                            }
                        }
                        if (!found) {
                            bindings[maxBindings++] = usesRef.resolvedType;
                        }
                    }
                } catch (Exception e) {
                    scope.problemReporter().javadocInvalidUsesClass(usesRef.sourceStart, usesRef.sourceEnd);
                }
            }

            // Look for undocumented uses
            if (reportMissing) {
                for (int i = 0; i < usesSize; i++) {
                    UsesStatement uses = moduleDecl.uses[i];
                    boolean found = false;
                    for (int j = 0; j < maxBindings && !found; j++) {
                        TypeBinding binding = bindings[j];
                        if (uses.serviceInterface.getTypeBinding(scope).equals(binding)) {
                            found = true;
                        }
                    }
                    if (!found) {
                        scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart,
                                uses.sourceEnd, moduleDecl.binding.modifiers);
                    }
                }
            }
        }
    }

    /*
     * Resolve @provides tags while block scope
     */
    private void resolveProvidesTags(BlockScope scope, boolean reportMissing) {
        ModuleDeclaration moduleDecl = (ModuleDeclaration) scope.referenceContext();
        int providesTagsSize = this.providesReferences == null ? 0 : this.providesReferences.length;

        // If no referenced module then report a problem for each uses tag
        if (moduleDecl == null) {
            for (int i = 0; i < providesTagsSize; i++) {
                IJavadocTypeReference provides = this.providesReferences[i];
                scope.problemReporter().javadocUnexpectedTag(provides.getTagSourceStart(),
                        provides.getTagSourceEnd());
            }
            return;
        }

        // If no uses tags then report a problem for each uses reference
        int providesSize = moduleDecl.servicesCount;
        if (providesTagsSize == 0) {
            if (reportMissing) {
                for (int i = 0; i < providesSize; i++) {
                    ProvidesStatement provides = moduleDecl.services[i];
                    scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface,
                            provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers);
                }
            }
        } else {
            TypeBinding[] bindings = new TypeBinding[providesTagsSize];
            int maxBindings = 0;

            // Scan all @provides tags
            for (int i = 0; i < providesTagsSize; i++) {
                TypeReference providesRef = (TypeReference) this.providesReferences[i];
                try {
                    providesRef.resolve(scope);
                    if (providesRef.resolvedType != null && providesRef.resolvedType.isValidBinding()) {
                        // Verify duplicated tags
                        boolean found = false;
                        for (int j = 0; j < maxBindings && !found; j++) {
                            if (bindings[j].equals(providesRef.resolvedType)) {
                                scope.problemReporter().javadocDuplicatedProvidesTag(providesRef.sourceStart,
                                        providesRef.sourceEnd);
                                found = true;
                            }
                        }
                        if (!found) {
                            bindings[maxBindings++] = providesRef.resolvedType;
                        }
                    }
                } catch (Exception e) {
                    scope.problemReporter().javadocInvalidProvidesClass(providesRef.sourceStart,
                            providesRef.sourceEnd);
                }
            }

            // Look for undocumented uses
            if (reportMissing) {
                for (int i = 0; i < providesSize; i++) {
                    ProvidesStatement provides = moduleDecl.services[i];
                    boolean found = false;
                    for (int j = 0; j < maxBindings && !found; j++) {
                        TypeBinding binding = bindings[j];
                        if (provides.serviceInterface.getTypeBinding(scope).equals(binding)) {
                            found = true;
                        }
                    }
                    if (!found) {
                        scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface,
                                provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers);
                    }
                }
            }
        }
    }

    /*
     * Resolve @param tags for type parameters
     */
    private void resolveTypeParameterTags(Scope scope, boolean reportMissing) {
        int paramTypeParamLength = this.paramTypeParameters == null ? 0 : this.paramTypeParameters.length;

        // Get declaration infos
        TypeParameter[] parameters = null;
        TypeVariableBinding[] typeVariables = null;
        int modifiers = -1;
        switch (scope.kind) {
        case Scope.METHOD_SCOPE:
            AbstractMethodDeclaration methodDeclaration = ((MethodScope) scope).referenceMethod();
            // If no referenced method (field initializer for example) then report a problem for each param tag
            if (methodDeclaration == null) {
                for (int i = 0; i < paramTypeParamLength; i++) {
                    JavadocSingleTypeReference param = this.paramTypeParameters[i];
                    scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd);
                }
                return;
            }
            parameters = methodDeclaration.typeParameters();
            typeVariables = methodDeclaration.binding.typeVariables;
            modifiers = methodDeclaration.binding.modifiers;
            break;
        case Scope.CLASS_SCOPE:
            TypeDeclaration typeDeclaration = ((ClassScope) scope).referenceContext;
            parameters = typeDeclaration.typeParameters;
            typeVariables = typeDeclaration.binding.typeVariables;
            modifiers = typeDeclaration.binding.modifiers;
            break;
        }

        // If no type variables then report a problem for each param type parameter tag
        if (typeVariables == null || typeVariables.length == 0) {
            for (int i = 0; i < paramTypeParamLength; i++) {
                JavadocSingleTypeReference param = this.paramTypeParameters[i];
                scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd);
            }
            return;
        }

        // If no param tags then report a problem for each declaration type parameter
        if (parameters != null) {
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4
            reportMissing = reportMissing && scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
            int typeParametersLength = parameters.length;
            if (paramTypeParamLength == 0) {
                if (reportMissing) {
                    for (int i = 0, l = typeParametersLength; i < l; i++) {
                        scope.problemReporter().javadocMissingParamTag(parameters[i].name,
                                parameters[i].sourceStart, parameters[i].sourceEnd, modifiers);
                    }
                }

                // Otherwise verify that all param tags match type parameters
            } else if (typeVariables.length == typeParametersLength) {
                TypeVariableBinding[] bindings = new TypeVariableBinding[paramTypeParamLength];

                // Scan all @param tags
                for (int i = 0; i < paramTypeParamLength; i++) {
                    JavadocSingleTypeReference param = this.paramTypeParameters[i];
                    TypeBinding paramBindind = param.internalResolveType(scope, 0);
                    if (paramBindind != null && paramBindind.isValidBinding()) {
                        if (paramBindind.isTypeVariable()) {
                            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=397888
                            if (scope.compilerOptions().reportUnusedParameterIncludeDocCommentReference) {
                                TypeVariableBinding typeVariableBinding = (TypeVariableBinding) paramBindind;
                                typeVariableBinding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed;
                            }
                            // Verify duplicated tags
                            boolean duplicate = false;
                            for (int j = 0; j < i && !duplicate; j++) {
                                if (TypeBinding.equalsEquals(bindings[j], param.resolvedType)) {
                                    scope.problemReporter().javadocDuplicatedParamTag(param.token,
                                            param.sourceStart, param.sourceEnd, modifiers);
                                    duplicate = true;
                                }
                            }
                            if (!duplicate) {
                                bindings[i] = (TypeVariableBinding) param.resolvedType;
                            }
                        } else {
                            scope.problemReporter().javadocUndeclaredParamTagName(param.token, param.sourceStart,
                                    param.sourceEnd, modifiers);
                        }
                    }
                }

                // Look for undocumented type parameters
                for (int i = 0; i < typeParametersLength; i++) {
                    TypeParameter parameter = parameters[i];
                    boolean found = false;
                    for (int j = 0; j < paramTypeParamLength && !found; j++) {
                        if (TypeBinding.equalsEquals(parameter.binding, bindings[j])) {
                            found = true;
                            bindings[j] = null;
                        }
                    }
                    if (!found && reportMissing) {
                        scope.problemReporter().javadocMissingParamTag(parameter.name, parameter.sourceStart,
                                parameter.sourceEnd, modifiers);
                    }
                }

                // Report invalid param
                for (int i = 0; i < paramTypeParamLength; i++) {
                    if (bindings[i] != null) {
                        JavadocSingleTypeReference param = this.paramTypeParameters[i];
                        scope.problemReporter().javadocUndeclaredParamTagName(param.token, param.sourceStart,
                                param.sourceEnd, modifiers);
                    }
                }
            }
        }
    }

    /*
     * Resolve @throws/@exception tags while method scope
     */
    private void resolveThrowsTags(MethodScope methScope, boolean reportMissing) {
        AbstractMethodDeclaration md = methScope.referenceMethod();
        int throwsTagsLength = this.exceptionReferences == null ? 0 : this.exceptionReferences.length;

        // If no referenced method (field initializer for example) then report a problem for each throws tag
        if (md == null) {
            for (int i = 0; i < throwsTagsLength; i++) {
                TypeReference typeRef = this.exceptionReferences[i];
                int start = typeRef.sourceStart;
                int end = typeRef.sourceEnd;
                if (typeRef instanceof JavadocQualifiedTypeReference) {
                    start = ((JavadocQualifiedTypeReference) typeRef).tagSourceStart;
                    end = ((JavadocQualifiedTypeReference) typeRef).tagSourceEnd;
                } else if (typeRef instanceof JavadocSingleTypeReference) {
                    start = ((JavadocSingleTypeReference) typeRef).tagSourceStart;
                    end = ((JavadocSingleTypeReference) typeRef).tagSourceEnd;
                }
                methScope.problemReporter().javadocUnexpectedTag(start, end);
            }
            return;
        }

        // If no throws tags then report a problem for each method thrown exception
        int boundExceptionLength = (md.binding == null) ? 0 : md.binding.thrownExceptions.length;
        int thrownExceptionLength = md.thrownExceptions == null ? 0 : md.thrownExceptions.length;
        if (throwsTagsLength == 0) {
            if (reportMissing) {
                for (int i = 0; i < boundExceptionLength; i++) {
                    ReferenceBinding exceptionBinding = md.binding.thrownExceptions[i];
                    if (exceptionBinding != null && exceptionBinding.isValidBinding()) { // flag only valid class name
                        int j = i;
                        while (j < thrownExceptionLength
                                && TypeBinding.notEquals(exceptionBinding, md.thrownExceptions[j].resolvedType))
                            j++;
                        if (j < thrownExceptionLength) {
                            methScope.problemReporter().javadocMissingThrowsTag(md.thrownExceptions[j],
                                    md.binding.modifiers);
                        }
                    }
                }
            }
        } else {
            int maxRef = 0;
            TypeReference[] typeReferences = new TypeReference[throwsTagsLength];

            // Scan all @throws tags
            for (int i = 0; i < throwsTagsLength; i++) {
                TypeReference typeRef = this.exceptionReferences[i];
                typeRef.resolve(methScope);
                TypeBinding typeBinding = typeRef.resolvedType;

                if (typeBinding != null && typeBinding.isValidBinding() && typeBinding.isClass()) {
                    // accept only valid class binding
                    typeReferences[maxRef++] = typeRef;
                }
            }

            // Look for undocumented thrown exception
            for (int i = 0; i < boundExceptionLength; i++) {
                ReferenceBinding exceptionBinding = md.binding.thrownExceptions[i];
                if (exceptionBinding != null)
                    exceptionBinding = (ReferenceBinding) exceptionBinding.erasure();
                boolean found = false;
                for (int j = 0; j < maxRef && !found; j++) {
                    if (typeReferences[j] != null) {
                        TypeBinding typeBinding = typeReferences[j].resolvedType;
                        if (TypeBinding.equalsEquals(exceptionBinding, typeBinding)) {
                            found = true;
                            typeReferences[j] = null;
                        }
                    }
                }
                if (!found && reportMissing) {
                    if (exceptionBinding != null && exceptionBinding.isValidBinding()) { // flag only valid class name
                        int k = i;
                        while (k < thrownExceptionLength
                                && TypeBinding.notEquals(exceptionBinding, md.thrownExceptions[k].resolvedType))
                            k++;
                        if (k < thrownExceptionLength) {
                            methScope.problemReporter().javadocMissingThrowsTag(md.thrownExceptions[k],
                                    md.binding.modifiers);
                        }
                    }
                }
            }

            // Verify additional @throws tags
            for (int i = 0; i < maxRef; i++) {
                TypeReference typeRef = typeReferences[i];
                if (typeRef != null) {
                    boolean compatible = false;
                    // thrown exceptions subclasses are accepted
                    for (int j = 0; j < thrownExceptionLength && !compatible; j++) {
                        TypeBinding exceptionBinding = md.thrownExceptions[j].resolvedType;
                        if (exceptionBinding != null) {
                            compatible = typeRef.resolvedType.isCompatibleWith(exceptionBinding);
                        }
                    }

                    //  If not compatible only complain on unchecked exception
                    if (!compatible && !typeRef.resolvedType.isUncheckedException(false)) {
                        methScope.problemReporter().javadocInvalidThrowsClassName(typeRef, md.binding.modifiers);
                    }
                }
            }
        }
    }

    private void verifyTypeReference(Expression reference, Expression typeReference, Scope scope, boolean source15,
            ReferenceBinding resolvedType, int modifiers) {
        if (resolvedType.isValidBinding()) {
            int scopeModifiers = -1;

            // reference must have enough visibility to be used
            if (!canBeSeen(scope.problemReporter().options.reportInvalidJavadocTagsVisibility, modifiers)) {
                scope.problemReporter().javadocHiddenReference(typeReference.sourceStart, reference.sourceEnd,
                        scope, modifiers);
                return;
            }

            // type reference must have enough visibility to be used
            if (reference != typeReference) {
                if (!canBeSeen(scope.problemReporter().options.reportInvalidJavadocTagsVisibility,
                        resolvedType.modifiers)) {
                    scope.problemReporter().javadocHiddenReference(typeReference.sourceStart,
                            typeReference.sourceEnd, scope, resolvedType.modifiers);
                    return;
                }
            }

            // member types
            if (resolvedType.isMemberType()) {
                ReferenceBinding topLevelType = resolvedType;
                // rebuild and store (in reverse order) compound name to handle embedded inner class
                int packageLength = topLevelType.fPackage.compoundName.length;
                int depth = resolvedType.depth();
                int idx = depth + packageLength;
                char[][] computedCompoundName = new char[idx + 1][];
                computedCompoundName[idx] = topLevelType.sourceName;
                while (topLevelType.enclosingType() != null) {
                    topLevelType = topLevelType.enclosingType();
                    computedCompoundName[--idx] = topLevelType.sourceName;
                }

                // add package information
                for (int i = packageLength; --i >= 0;) {
                    computedCompoundName[--idx] = topLevelType.fPackage.compoundName[i];
                }

                ClassScope topLevelScope = scope.classScope();
                // when scope is not on compilation unit type, then inner class may not be visible...
                if (topLevelScope.parent.kind != Scope.COMPILATION_UNIT_SCOPE
                        || !CharOperation.equals(topLevelType.sourceName, topLevelScope.referenceContext.name)) {
                    topLevelScope = topLevelScope.outerMostClassScope();
                    if (typeReference instanceof JavadocSingleTypeReference) {
                        // inner class single reference can only be done in same unit
                        if ((!source15 && depth == 1)
                                || TypeBinding.notEquals(topLevelType, topLevelScope.referenceContext.binding)) {
                            // search for corresponding import
                            boolean hasValidImport = false;
                            if (source15) {
                                CompilationUnitScope unitScope = topLevelScope.compilationUnitScope();
                                ImportBinding[] imports = unitScope.imports;
                                int length = imports == null ? 0 : imports.length;
                                mainLoop: for (int i = 0; i < length; i++) {
                                    char[][] compoundName = imports[i].compoundName;
                                    int compoundNameLength = compoundName.length;
                                    if ((imports[i].onDemand
                                            && compoundNameLength == computedCompoundName.length - 1)
                                            || (compoundNameLength == computedCompoundName.length)) {
                                        for (int j = compoundNameLength; --j >= 0;) {
                                            if (CharOperation.equals(imports[i].compoundName[j],
                                                    computedCompoundName[j])) {
                                                if (j == 0) {
                                                    hasValidImport = true;
                                                    ImportReference importReference = imports[i].reference;
                                                    if (importReference != null) {
                                                        importReference.bits |= ASTNode.Used;
                                                    }
                                                    break mainLoop;
                                                }
                                            } else {
                                                break;
                                            }
                                        }
                                    }
                                }
                                if (!hasValidImport) {
                                    if (scopeModifiers == -1)
                                        scopeModifiers = scope.getDeclarationModifiers();
                                    scope.problemReporter().javadocInvalidMemberTypeQualification(
                                            typeReference.sourceStart, typeReference.sourceEnd, scopeModifiers);
                                }
                            } else {
                                if (scopeModifiers == -1)
                                    scopeModifiers = scope.getDeclarationModifiers();
                                scope.problemReporter().javadocInvalidMemberTypeQualification(
                                        typeReference.sourceStart, typeReference.sourceEnd, scopeModifiers);
                                return;
                            }
                        }
                    }
                }
                if (typeReference instanceof JavadocQualifiedTypeReference
                        && !scope.isDefinedInSameUnit(resolvedType)) {
                    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=222188
                    // partially qualified references from a different CU should be warned
                    char[][] typeRefName = ((JavadocQualifiedTypeReference) typeReference).getTypeName();
                    int skipLength = 0;
                    if (topLevelScope.getCurrentPackage() == resolvedType.getPackage()
                            && typeRefName.length < computedCompoundName.length) {
                        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=221539: references can be partially qualified
                        // in same package and hence if the package name is not given, ignore package name check
                        skipLength = resolvedType.fPackage.compoundName.length;
                    }
                    boolean valid = true;
                    if (typeRefName.length == computedCompoundName.length - skipLength) {
                        checkQualification: for (int i = 0; i < typeRefName.length; i++) {
                            if (!CharOperation.equals(typeRefName[i], computedCompoundName[i + skipLength])) {
                                valid = false;
                                break checkQualification;
                            }
                        }
                    } else {
                        valid = false;
                    }
                    // report invalid reference
                    if (!valid) {
                        if (scopeModifiers == -1)
                            scopeModifiers = scope.getDeclarationModifiers();
                        scope.problemReporter().javadocInvalidMemberTypeQualification(typeReference.sourceStart,
                                typeReference.sourceEnd, scopeModifiers);
                        return;
                    }
                }
            }
            /*
             * https://bugs.eclipse.org/bugs/show_bug.cgi?id=286918
             *
             * We are concerned only about the Single type references (i.e. without any package) If they are not in default package,
             * then report an error. References with qualified yet incorrect names would have already been taken care of.
             */
            if (scope.referenceCompilationUnit().isPackageInfo()
                    && typeReference instanceof JavadocSingleTypeReference) {
                if (resolvedType.fPackage.compoundName.length > 0) {
                    scope.problemReporter().javadocInvalidReference(typeReference.sourceStart,
                            typeReference.sourceEnd);
                    return; // Not really needed - just in case more code added in future
                }
            }
        }
    }

    @Override
    public void traverse(ASTVisitor visitor, BlockScope scope) {
        if (visitor.visit(this, scope)) {
            if (this.paramReferences != null) {
                for (int i = 0, length = this.paramReferences.length; i < length; i++) {
                    this.paramReferences[i].traverse(visitor, scope);
                }
            }
            if (this.paramTypeParameters != null) {
                for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) {
                    this.paramTypeParameters[i].traverse(visitor, scope);
                }
            }
            if (this.returnStatement != null) {
                this.returnStatement.traverse(visitor, scope);
            }
            if (this.exceptionReferences != null) {
                for (int i = 0, length = this.exceptionReferences.length; i < length; i++) {
                    this.exceptionReferences[i].traverse(visitor, scope);
                }
            }
            if (this.seeReferences != null) {
                for (int i = 0, length = this.seeReferences.length; i < length; i++) {
                    this.seeReferences[i].traverse(visitor, scope);
                }
            }
        }
        visitor.endVisit(this, scope);
    }

    public void traverse(ASTVisitor visitor, ClassScope scope) {
        if (visitor.visit(this, scope)) {
            if (this.paramReferences != null) {
                for (int i = 0, length = this.paramReferences.length; i < length; i++) {
                    this.paramReferences[i].traverse(visitor, scope);
                }
            }
            if (this.paramTypeParameters != null) {
                for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) {
                    this.paramTypeParameters[i].traverse(visitor, scope);
                }
            }
            if (this.returnStatement != null) {
                this.returnStatement.traverse(visitor, scope);
            }
            if (this.exceptionReferences != null) {
                for (int i = 0, length = this.exceptionReferences.length; i < length; i++) {
                    this.exceptionReferences[i].traverse(visitor, scope);
                }
            }
            if (this.seeReferences != null) {
                for (int i = 0, length = this.seeReferences.length; i < length; i++) {
                    this.seeReferences[i].traverse(visitor, scope);
                }
            }
        }
        visitor.endVisit(this, scope);
    }
}