org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.api.tools.internal.builder;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.pde.api.tools.internal.model.ApiType;
import org.eclipse.pde.api.tools.internal.model.ProjectComponent;
import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants;
import org.eclipse.pde.api.tools.internal.provisional.builder.IApiProblemDetector;
import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiField;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMethod;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.util.Signatures;
import org.eclipse.pde.api.tools.internal.util.Util;

/**
 * @since 1.1
 */
public abstract class AbstractProblemDetector implements IApiProblemDetector {

    public static final String METHOD_REFERENCE = "::"; //$NON-NLS-1$
    public static final String CONSTRUCTOR_NEW = "new"; //$NON-NLS-1$

    /**
     * Class used to look up the name of the enclosing method for an
     * {@link IApiType} when we do not have any enclosing method infos (pre Java
     * 1.5 class files
     */
    class MethodFinder extends ASTVisitor {
        IMethod method = null;
        private IType jtype = null;
        private ApiType type = null;

        public MethodFinder(ApiType type, IType jtype) {
            this.type = type;
            this.jtype = jtype;
        }

        @Override
        public boolean visit(AnonymousClassDeclaration node) {
            if (method == null) {
                ITypeBinding binding = node.resolveBinding();
                String binaryName = binding.getBinaryName();
                if (type.getName().endsWith(binaryName)) {
                    try {
                        IJavaElement element = jtype.getCompilationUnit().getElementAt(node.getStartPosition());
                        if (element != null) {
                            IJavaElement ancestor = element.getAncestor(IJavaElement.METHOD);
                            if (ancestor != null) {
                                method = (IMethod) ancestor;
                            }
                        }
                    } catch (JavaModelException jme) {
                    }
                    return false;
                }
            }
            return true;
        }

        @Override
        public boolean visit(TypeDeclaration node) {
            if (method == null && node.isLocalTypeDeclaration()) {
                ITypeBinding binding = node.resolveBinding();
                String binaryName = binding.getBinaryName();
                if (type.getName().endsWith(binaryName)) {
                    try {
                        IJavaElement element = jtype.getCompilationUnit().getElementAt(node.getStartPosition());
                        if (element.getElementType() == IJavaElement.TYPE) {
                            IType ltype = (IType) element;
                            IJavaElement parent = ltype.getParent();
                            if (parent.getElementType() == IJavaElement.METHOD) {
                                method = (IMethod) parent;
                            }
                        }
                    } catch (JavaModelException jme) {
                    }
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * List of potential {@link IReference} problems
     */
    private List<IReference> fPotentialProblems = new LinkedList<IReference>();

    /**
     * Retains the reference for further analysis.
     * 
     * @param reference reference
     */
    protected void retainReference(IReference reference) {
        fPotentialProblems.add(reference);
    }

    /**
     * Return the list of retained references.
     * 
     * @return references
     */
    protected List<IReference> getRetainedReferences() {
        return fPotentialProblems;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.pde.api.tools.internal.provisional.builder.IApiProblemDetector
     * #
     * considerReference(org.eclipse.pde.api.tools.internal.provisional.builder.
     * IReference)
     */
    @Override
    public boolean considerReference(IReference reference) {
        return reference != null && (reference.getReferenceKind() & getReferenceKinds()) > 0;
    }

    /**
     * Creates a problem for a specific reference in the workspace
     * 
     * @param reference reference
     * @param associated java project (with reference source location)
     * @return problem or <code>null</code> if none
     * @exception CoreException if something goes wrong
     */
    protected IApiProblem createProblem(IReference reference, IJavaProject javaProject) {
        IProject project = javaProject.getProject();
        if (ApiPlugin.getDefault().getSeverityLevel(getSeverityKey(), project) == ApiPlugin.SEVERITY_IGNORE) {
            return null;
        }
        try {
            IApiMember member = reference.getMember();
            String lookupName = getTypeName(member).replace('$', '.');
            IType type = javaProject.findType(lookupName, new NullProgressMonitor());
            if (type == null) {
                return null;
            }
            ICompilationUnit compilationUnit = type.getCompilationUnit();
            if (compilationUnit == null) {
                return null;
            }
            IResource resource = Util.getResource(project, type);
            if (resource == null) {
                return null;
            }
            int charStart = -1;
            int charEnd = -1;
            int lineNumber = reference.getLineNumber();
            IJavaElement element = compilationUnit;
            if (!Util.isManifest(resource.getProjectRelativePath()) && !type.isBinary()) {
                IDocument document = Util.getDocument(compilationUnit);
                if (lineNumber > 0) {
                    // reference line number are 1-based, but the API problem
                    // line number are 0-based
                    // they will be converted to 1-based at marker creation time
                    lineNumber--;
                }
                // retrieve line number, char start and char end
                if ((reference.getReferenceKind() & (IReference.REF_OVERRIDE | IReference.REF_EXTENDS
                        | IReference.REF_IMPLEMENTS | IReference.REF_PARAMETER | IReference.REF_RETURNTYPE
                        | IReference.REF_THROWS)) != 0) {
                    IApiType enclosingType = member.getEnclosingType();
                    if (lineNumber > 0 && enclosingType != null && enclosingType.isAnonymous()) {
                        String superclass = enclosingType.getSuperclassName();
                        String name = null;
                        if ("java.lang.Object".equals(superclass)) { //$NON-NLS-1$
                            // check the super_interfaces
                            String[] superinterfaces = enclosingType.getSuperInterfaceNames();
                            if (superinterfaces != null) {
                                String superinterface = superinterfaces[0];
                                name = superinterface.substring(superinterface.lastIndexOf('.') + 1);
                            } else {
                                // this is really an anonymous class of Object
                                name = superclass.substring(superclass.lastIndexOf('.') + 1);
                            }
                        } else if (superclass != null) {
                            name = superclass.substring(superclass.lastIndexOf('.') + 1);
                        }
                        if (name != null) {
                            try {
                                IRegion lineInformation = document.getLineInformation(lineNumber);
                                String lineContents = document.get(lineInformation.getOffset(),
                                        lineInformation.getLength());
                                charStart = lineInformation.getOffset() + lineContents.indexOf(name);
                                charEnd = charStart + name.length();
                            } catch (BadLocationException e) {
                                ApiPlugin.log(e);
                                return null;
                            }
                        }
                    }
                }
                if (charStart == -1) {
                    // get the source range for the problem
                    try {
                        Position pos = getSourceRange(type, document, reference);
                        if (pos != null) {
                            charStart = pos.getOffset();
                            if (charStart != -1) {
                                charEnd = charStart + pos.getLength();
                                lineNumber = document.getLineOfOffset(charStart);
                            }
                        }
                    } catch (CoreException e) {
                        ApiPlugin.log(e);
                        return null;
                    } catch (BadLocationException e) {
                        ApiPlugin.log(e);
                        return null;
                    }
                }
                if (charStart > -1) {
                    element = compilationUnit.getElementAt(charStart);
                }
            }
            return ApiProblemFactory.newApiUsageProblem(resource.getProjectRelativePath().toPortableString(),
                    type.getFullyQualifiedName(), getMessageArgs(reference),
                    new String[] { IApiMarkerConstants.MARKER_ATTR_HANDLE_ID,
                            IApiMarkerConstants.API_MARKER_ATTR_ID },
                    new Object[] {
                            (element == null ? compilationUnit.getHandleIdentifier()
                                    : element.getHandleIdentifier()),
                            new Integer(IApiMarkerConstants.API_USAGE_MARKER_ID) },
                    lineNumber, // 0-based
                    charStart, charEnd, getElementType(reference), getProblemKind(), getProblemFlags(reference));
        } catch (CoreException e) {
            ApiPlugin.log(e);
        }
        return null;
    }

    /**
     * Returns the source range to include in the associated problem or
     * <code>null</code> if a valid source range could not be computed.
     * 
     * @param type resolved type where the reference occurs
     * @param doc source document of the type
     * @param reference associated reference
     * @return source range as a position
     */
    protected abstract Position getSourceRange(IType type, IDocument doc, IReference reference)
            throws CoreException, BadLocationException;

    /**
     * Returns the element type the problem is reported on.
     * 
     * @return
     */
    protected abstract int getElementType(IReference reference);

    /**
     * Returns problem flags, if any.
     * 
     * @param reference
     * @return problem flags
     */
    protected abstract int getProblemFlags(IReference reference);

    /**
     * Returns problem message arguments
     * 
     * @return message arguments
     */
    protected abstract String[] getMessageArgs(IReference reference) throws CoreException;

    /**
     * Returns problem message arguments to be used in headless build
     * 
     * @return message arguments
     */
    protected abstract String[] getQualifiedMessageArgs(IReference reference) throws CoreException;

    /**
     * Returns the kind of problem to create
     * 
     * @return problem kind
     */
    protected abstract int getProblemKind();

    /**
     * Returns the key used to lookup problem severity.
     * 
     * @return problem severity key
     */
    protected abstract String getSeverityKey();

    /**
     * Returns the fully qualified type name associated with the given member.
     * 
     * @param member
     * @return fully qualified type name
     */
    protected String getTypeName(IApiMember member) throws CoreException {
        switch (member.getType()) {
        case IApiElement.TYPE: {
            IApiType type = (IApiType) member;
            if (type.isAnonymous()) {
                return getTypeName(member.getEnclosingType());
            } else if (type.isLocal()) {
                return getTypeName(member.getEnclosingType());
            }
            return member.getName();
        }
        default: {
            return getTypeName(member.getEnclosingType());
        }
        }
    }

    /**
     * Returns the qualified type name to display. This method delegates to the
     * {@link Signatures} class to build the display signatures
     * 
     * @param member
     * @return fully qualified display signature for the given {@link IApiType}
     *         or enclosing type if the member is not a type itself
     * @throws CoreException
     */
    protected String getQualifiedTypeName(IApiMember member) throws CoreException {
        switch (member.getType()) {
        case IApiElement.TYPE: {
            IApiType type = (IApiType) member;
            if (type.isAnonymous()) {
                return getQualifiedTypeName(member.getEnclosingType());
            } else if (type.isLocal()) {
                String name = getTypeName(member.getEnclosingType());
                int idx = name.indexOf('$');
                if (idx > -1) {
                    return name.substring(0, idx);
                }
                return name;
            }
            return Signatures.getQualifiedTypeSignature((IApiType) member);
        }
        default: {
            return getQualifiedTypeName(member.getEnclosingType());
        }
        }
    }

    /**
     * Returns the unqualified type name associated with the given member.
     * 
     * @param member
     * @return unqualified type name
     */
    protected String getSimpleTypeName(IApiMember member) throws CoreException {
        switch (member.getType()) {
        case IApiElement.TYPE: {
            IApiType type = (IApiType) member;
            if (type.isAnonymous()) {
                return getSimpleTypeName(type.getEnclosingType());
            } else if (type.isLocal()) {
                String name = getSimpleTypeName(member.getEnclosingType());
                int idx = name.indexOf('$');
                if (idx > -1) {
                    return name.substring(0, idx);
                }
                return name;
            }
            return Signatures.getTypeName(Signatures.getTypeSignature(type));
        }
        default:
            return getSimpleTypeName(member.getEnclosingType());
        }
    }

    /**
     * Default strategy for when no source position can be computed: creates a
     * {@link Position} for the name of the given {@link IType}. Returns
     * <code>null</code> in the event the given {@link IType} is
     * <code>null</code> or the name range cannot be computed for the type.
     * 
     * @param type the type
     * @param reference the reference
     * @throws CoreException
     * @return returns a default {@link Position} for the name range of the
     *         given {@link IType}
     */
    protected Position defaultSourcePosition(IType type, IReference reference) throws CoreException {
        if (type != null) {
            ISourceRange range = type.getNameRange();
            if (range != null) {
                return new Position(range.getOffset(), range.getLength());
            }
        }
        return null;
    }

    /**
     * Finds the method name to select on the given line of code starting from
     * the given index. This method will recurse to find a method name in the
     * even there is a name clash with the type. For example:
     * 
     * <pre>
     * MyType type = new MyType();
     * </pre>
     * 
     * If we are trying to find the constructor method call we have a name
     * collision (and the first occurrence of MyType would be selected). <br>
     * A name is determined to be a method name if it is followed by a '('
     * character (excluding spaces)
     * 
     * @param namepart
     * @param line
     * @param index
     * @return the index of the method name on the given line or -1 if not found
     */
    protected int findMethodNameStart(String namepart, String line, int index) {
        if (namepart.startsWith(METHOD_REFERENCE)) {
            // a method ref, walk back to find the token
            int offset = index;
            char c = line.charAt(offset);
            while (!Character.isJavaIdentifierPart((int) c)) {
                offset--;
                c = line.charAt(offset);
            }
            while (Character.isJavaIdentifierPart((int) c)) {
                offset--;
                c = line.charAt(offset);
                if (c == '<') {
                    // might encounter the opening bound, skip it
                    c = line.charAt(--offset);
                }
            }
            offset++;
            return offset;
        } else {
            int start = line.indexOf(namepart, index);
            if (start < 0) {
                return -1;
            }
            int offset = start + namepart.length();
            char c = line.charAt(offset);
            while (c == ' ') {
                offset++;
                c = line.charAt(offset);
            }

            if (c == '(' || c == '<') {
                return start;
            }

            // assumes that "::" & method name/"new" in same line
            if (line.contains(METHOD_REFERENCE)) {
                if ((c == ';') || (c == '\r') || (c == ')')) {
                    return start;
                }
                // method reference constructor
                if ((c == ':') && line.charAt(offset + 1) == ':' && line.contains(CONSTRUCTOR_NEW)) {
                    return start;
                }

            }
            return findMethodNameStart(namepart, line, offset);
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.pde.api.tools.internal.provisional.search.IApiProblemDetector
     * #createProblems()
     */
    @Override
    public List<IApiProblem> createProblems() {
        List<IReference> references = getRetainedReferences();
        if (references.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<IApiProblem> problems = new LinkedList<IApiProblem>();
        Iterator<IReference> iterator = references.iterator();
        while (iterator.hasNext()) {
            IReference reference = iterator.next();
            if (reference.getResolvedReference() == null) {
                // unresolved reference ignore it
            } else {
                if (isProblem(reference)) {
                    try {
                        IApiProblem problem = null;
                        IApiComponent component = reference.getMember().getApiComponent();
                        if (component instanceof ProjectComponent) {
                            ProjectComponent ppac = (ProjectComponent) component;
                            IJavaProject project = ppac.getJavaProject();
                            problem = createProblem(reference, project);
                        } else {
                            problem = createProblem(reference);
                        }
                        if (problem != null) {
                            problems.add(problem);
                        }
                    } catch (CoreException e) {
                        ApiPlugin.log(e.getStatus());
                    }
                }
            }
        }
        return problems;
    }

    /**
     * Returns whether the resolved reference is a real problem.
     * 
     * @param reference
     * @return whether a problem
     */
    protected boolean isProblem(IReference reference) {
        // by default fragment -> host references are not problems
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=255659
        IApiMember member = reference.getResolvedReference();
        if (member != null) {
            IApiMember local = reference.getMember();
            try {
                IApiComponent lcomp = local.getApiComponent();
                if (lcomp != null && lcomp.isFragment()) {
                    return !lcomp.getHost().equals(member.getApiComponent());
                }
            } catch (CoreException ce) {
                ApiPlugin.log(ce);
            }
        }
        return true;
    }

    protected boolean isReferenceFromComponent(IReference reference, String componentId) {
        if (componentId != null) {
            final IApiComponent apiComponent = reference.getResolvedReference().getApiComponent();
            // API component is either component id itself or one of its
            // fragment
            if (apiComponent.getSymbolicName().equals(componentId)) {
                return true;
            }
            try {
                final IApiComponent host = apiComponent.getHost();
                return host != null && host.getSymbolicName().equals(componentId);
            } catch (CoreException e) {
                ApiPlugin.log(e);
            }
        }
        return false;
    }

    /**
     * Tries to find the given {@link IApiMethod} in the given {@link IType}. If
     * a matching method is not found <code>null</code> is returned
     * 
     * @param type the type top look in for the given {@link IApiMethod}
     * @param method the {@link IApiMethod} to look for
     * @return the {@link IMethod} from the given {@link IType} that matches the
     *         given {@link IApiMethod} or <code>null</code> if no matching
     *         method is found
     * @throws JavaModelException
     * @throws CoreException
     */
    protected IMethod findMethodInType(IType type, IApiMethod method) throws JavaModelException, CoreException {
        String[] parameterTypes = Signature.getParameterTypes(method.getSignature());
        for (int i = 0; i < parameterTypes.length; i++) {
            parameterTypes[i] = parameterTypes[i].replace('/', '.');
        }
        String methodname = method.getName();
        if (method.isConstructor()) {
            IApiType enclosingType = method.getEnclosingType();
            if (enclosingType.isMemberType() && !Flags.isStatic(enclosingType.getModifiers())) {
                // remove the synthetic argument that corresponds to the
                // enclosing type
                int length = parameterTypes.length - 1;
                System.arraycopy(parameterTypes, 1, (parameterTypes = new String[length]), 0, length);
            }
            methodname = enclosingType.getSimpleName();
        }
        IMethod Qmethod = type.getMethod(methodname, parameterTypes);
        IMethod[] methods = type.getMethods();
        IMethod match = null;
        for (int i = 0; i < methods.length; i++) {
            IMethod m = methods[i];
            if (m.isSimilar(Qmethod)) {
                match = m;
                break;
            }
        }
        return match;
    }

    /**
     * Tries to find the given {@link IApiField} in the given {@link IType}. If
     * the field cannot be found <code>null</code> is returned
     * 
     * @param type
     * @param field
     * @return the {@link IField} matching the given {@link IApiField} or
     *         <code>null</code>
     * @since 1.0.600
     * @throws JavaModelException
     */
    protected IField findFieldInType(IType type, IApiField field) throws JavaModelException {
        IField match = null;
        match = type.getField(field.getName());
        if (!match.exists()) {
            IField[] fields = type.getFields();
            // optimistically try to find the first match
            for (int i = 0; i < fields.length; i++) {
                if (fields[i].getElementName().equals(field.getName())) {
                    match = fields[i];
                    break;
                }
            }
        }
        return match;
    }

    /**
     * Tries to find the given {@link IApiType} in the given {@link IType}. If
     * no match is found <code>null</code> is returned.
     * 
     * @param type
     * @param apitype
     * @param reference
     * @param doc
     * @return the matching {@link IType} or <code>null</code>
     * @since 1.0.600
     * @throws CoreException
     * @throws JavaModelException
     */
    protected IType findTypeInType(IType type, IApiType apitype, IReference reference, IDocument doc)
            throws CoreException, JavaModelException {
        if (apitype.isLocal()) {
            String name = apitype.getSimpleName();
            ICompilationUnit cunit = type.getCompilationUnit();
            if (cunit.isWorkingCopy()) {
                cunit.reconcile(AST.JLS8, false, null, null);
            }
            IMethod method = getEnclosingMethod(type, reference, doc);
            if (method != null) {
                return method.getType(name, 1);
            }
        }
        String tname = type.getElementName();
        if (tname.equals(apitype.getName()) || tname.equals(apitype.getSimpleName())) {
            return type;
        }
        IType match = null;
        IType[] types = type.getTypes();
        for (int i = 0; i < types.length; i++) {
            if ((types[i].getElementName().equals(apitype.getName()))) {
                match = types[i];
                break;
            }
        }
        return match;
    }

    /**
     * Returns the enclosing {@link IMethod} for the given type or
     * <code>null</code> if it cannot be computed
     * 
     * @param type
     * @param jtype
     * @param reference
     * @param document
     * @return the {@link IMethod} enclosing the given type or <code>null</code>
     * @throws CoreException
     */
    protected IMethod getEnclosingMethod(final IType jtype, IReference reference, IDocument document)
            throws CoreException {
        IApiMember member = reference.getMember();
        if ((member.getType() == IApiElement.TYPE)) {
            ApiType type = (ApiType) member;
            IApiMethod apimethod = type.getEnclosingMethod();
            if (apimethod != null) {
                String signature = Signatures.processMethodSignature(apimethod);
                String methodname = Signatures.getMethodName(apimethod);
                IMethod method = jtype.getMethod(methodname, Signature.getParameterTypes(signature));
                if (method.exists()) {
                    return method;
                }
            } else {
                // try to look it up
                IMethod method = null;
                if (reference.getLineNumber() > -1) {
                    try {
                        int offset = document.getLineOffset(reference.getLineNumber());
                        method = quickLookup(jtype, document, reference, offset);
                    } catch (BadLocationException ble) {
                    }
                }
                if (method == null) {
                    // look it up the hard way
                    ISourceRange range = jtype.getCompilationUnit().getSourceRange();
                    ASTParser parser = ASTParser.newParser(AST.JLS8);
                    parser.setSource(jtype.getCompilationUnit());
                    parser.setSourceRange(range.getOffset(), range.getLength());
                    parser.setResolveBindings(true);
                    ASTNode ptype = parser.createAST(null);
                    MethodFinder finder = new MethodFinder(type, jtype);
                    ptype.accept(finder);
                    method = finder.method;
                }
                if (method != null && method.exists()) {
                    ApiType etype = (ApiType) type.getEnclosingType();
                    IApiMethod[] methods = etype.getMethods();
                    String msig = null;
                    for (int i = 0; i < methods.length; i++) {
                        msig = methods[i].getSignature();
                        if (Signatures.getMethodName(methods[i]).equals(method.getElementName())
                                && Signatures.matchesSignatures(msig.replace('/', '.'), method.getSignature())) {
                            type.setEnclosingMethodInfo(methods[i].getName(), msig);
                        }
                    }
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * Performs a quick look-up using the offset into the the
     * {@link ICompilationUnit}
     * 
     * @param jtype
     * @param document
     * @param reference
     * @param offset
     * @return
     * @throws JavaModelException
     */
    protected IMethod quickLookup(final IType jtype, IDocument document, IReference reference, int offset)
            throws JavaModelException {
        if (offset > -1) {
            IJavaElement element = jtype.getCompilationUnit().getElementAt(offset);
            if (element != null) {
                IJavaElement ancestor = element.getAncestor(IJavaElement.METHOD);
                if (ancestor != null) {
                    return (IMethod) ancestor;
                }
            }
        }
        return null;
    }

    /**
     * Returns the source range for the given {@link IApiMethod} within the
     * given {@link IType}
     * 
     * @param type the type to look for the method within
     * @param reference the reference the method comes from
     * @param method the {@link IApiMethod} to look for the source range for
     * @return the {@link ISourceRange} in the {@link IType} enclosing the given
     *         {@link IApiMethod}
     * @throws CoreException
     * @throws JavaModelException
     */
    protected Position getSourceRangeForMethod(IType type, IReference reference, IApiMethod method)
            throws CoreException, JavaModelException {
        IMethod match = findMethodInType(type, method);
        Position pos = null;
        if (match != null) {
            ISourceRange range = match.getNameRange();
            if (range != null) {
                pos = new Position(range.getOffset(), range.getLength());
            }
        }
        if (pos == null) {
            return defaultSourcePosition(type, reference);
        }
        return pos;
    }

    /**
     * Returns the source range to use for the given field within the given
     * {@link IType}
     * 
     * @param type the type to look in for the given {@link IApiField}
     * @param reference the reference the field is involved in
     * @param field the field to find the range for
     * @return the {@link ISourceRange} in the given {@link IType} that encloses
     *         the given {@link IApiField}
     * @throws JavaModelException
     * @throws CoreException
     */
    protected Position getSourceRangeForField(IType type, IReference reference, IApiField field)
            throws JavaModelException, CoreException {
        IField javaField = type.getField(field.getName());
        Position pos = null;
        if (javaField.exists()) {
            ISourceRange range = javaField.getNameRange();
            if (range != null) {
                pos = new Position(range.getOffset(), range.getLength());
            }
        }
        if (pos == null) {
            return defaultSourcePosition(type, reference);
        }
        return pos;
    }

    /**
     * Returns the range of the name of the given {@link IApiField} to select
     * when creating {@link IApiProblem}s. Source ranges are computed and tried
     * in the following order:
     * <ol>
     * <li>Try the type-qualified name of the variable</li>
     * <li>Try looking for 'super.variable'</li>
     * <li>Try looking for 'this.variable'</li>
     * <li>Try looking for pattern '*.variable'</li>
     * <li>Else select the entire line optimistically</li>
     * </ol>
     * 
     * @param field the field to find the name range for
     * @param document the document to look within
     * @param reference the reference the field is from
     * @return the range of text to select, or <code>null</code> if one could
     *         not be computed
     * @throws BadLocationException
     * @throws CoreException
     */
    protected Position getFieldNameRange(IApiField field, IDocument document, IReference reference)
            throws BadLocationException, CoreException {
        return getFieldNameRange(field.getEnclosingType().getName(), field.getName(), document, reference);
    }

    protected Position getFieldNameRange(String typeName, String fieldName, IDocument document,
            IReference reference) throws BadLocationException {
        int linenumber = reference.getLineNumber();
        if (linenumber > 0) {
            // line number are 1-based for the reference, but 0-based for the
            // document
            linenumber--;
        }
        if (linenumber > 0) {
            int offset = document.getLineOffset(linenumber);
            String line = document.get(offset, document.getLineLength(linenumber));
            String qname = typeName + "." + fieldName; //$NON-NLS-1$
            int first = line.indexOf(qname);
            if (first < 0) {
                qname = "super." + fieldName; //$NON-NLS-1$
                first = line.indexOf(qname);
            }
            if (first < 0) {
                qname = "this." + fieldName; //$NON-NLS-1$
                first = line.indexOf(qname);
            }
            if (first < 0) {
                // try a pattern [.*field_name]
                // the field might be ref'd via a constant, e.g. enum constant
                int idx = line.indexOf(fieldName);
                while (idx > -1) {
                    if (line.charAt(idx - 1) == '.') {
                        first = idx;
                        qname = fieldName;
                        break;
                    }
                    idx = line.indexOf(fieldName, idx + 1);
                }
            }
            Position pos = null;
            if (first > -1) {
                pos = new Position(offset + first, qname.length());
            } else {
                // optimistically select the whole line since we can't find the
                // correct variable name and we can't just select
                // the first occurrence
                pos = new Position(offset, line.length());
            }
            return pos;
        }
        return null;
    }

    /**
     * Searches for the name of a method at the line number specified in the
     * given reference.
     * 
     * @param name method name
     * @param document document to search in
     * @param reference provides line number
     * @return method name range
     * @throws CoreException
     */
    protected Position getMethodNameRange(boolean isContructor, String name, IDocument document,
            IReference reference) throws CoreException, BadLocationException {
        int linenumber = reference.getLineNumber();
        if (linenumber > 0) {
            // line number are 1-based for the reference, but 0-based for the
            // document
            linenumber--;
        }
        String methodname = name;
        int idx = methodname.indexOf('$');
        if (idx > -1) {
            methodname = methodname.substring(0, idx);
        }
        idx = methodname.indexOf(Signatures.getLT());
        if (idx > -1) {
            methodname = methodname.substring(0, idx);
        }
        int offset = document.getLineOffset(linenumber);
        String line = document.get(offset, document.getLineLength(linenumber));
        int start = line.indexOf('=');
        if (start < 0) {
            if (isContructor) {
                // new keyword should only be checked if the method is a
                // constructor
                // what if space between the two?
                start = line.indexOf(METHOD_REFERENCE + CONSTRUCTOR_NEW);
                if (start < 0) {
                    line.indexOf(CONSTRUCTOR_NEW);
                    if (start < 0) {
                        start = 0;
                    }
                } else {
                    int first = findMethodNameStart(METHOD_REFERENCE + CONSTRUCTOR_NEW, line, start);
                    return new Position(offset + first, (start - first) + 5);
                }
            } else {
                start = 0;
            }
        } else {
            char charat = line.charAt(start - 1);
            // make sure its not '==' | '!=' | '<=' | '>='
            if (line.charAt(start + 1) == '=' || charat == '!' || charat == '<' || charat == '>') {
                start = 0;
            }
        }
        int first = findMethodNameStart(methodname, line, start);

        if (line.contains(METHOD_REFERENCE) && line.contains(CONSTRUCTOR_NEW) && isContructor) {
            String afterReference = line.substring(line.indexOf(METHOD_REFERENCE));
            methodname = afterReference.substring(afterReference.indexOf(METHOD_REFERENCE) + 2,
                    afterReference.indexOf(CONSTRUCTOR_NEW) + 3);
        }
        if (first < 0) {
            methodname = "super"; //$NON-NLS-1$
            first = findMethodNameStart(methodname, line, start);
        }
        if (first > -1) {
            idx = line.indexOf(METHOD_REFERENCE, first);
            if (idx > -1 && isContructor) {
                //a method ref, add the start + :: + method name length
                return new Position(offset + first, (idx - first) + 2 + methodname.length());
            }
            return new Position(offset + first, methodname.length());
        }
        return null;
    }

    /**
     * Returns if the signature is enclosed by the one of the elements of the
     * given set of type names.
     * 
     * @param signature the signature of the element
     * @param typenames the Set of {@link String}s of type names
     * @return <code>true</code> if the given set contains a type name that
     *         encloses the given signature, <code>false</code> otherwise
     * 
     * @since 1.0.400
     */
    boolean isEnclosedBy(String signature, Set<String> typenames) {
        if (signature == null || typenames == null) {
            return false;
        }
        if (typenames.contains(signature)) {
            return true;
        }
        StringTokenizer tokenizer = new StringTokenizer(signature, "$"); //$NON-NLS-1$
        while (tokenizer.hasMoreTokens()) {
            if (typenames.contains(tokenizer.nextToken())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param reference
     * @return
     * @throws CoreException
     */
    public IApiProblem createProblem(IReference reference) throws CoreException {
        int lineNumber = reference.getLineNumber();
        if (lineNumber > 0) {
            lineNumber--;
        }
        String ltypename = getTypeName(reference.getMember());
        return ApiProblemFactory.newApiUsageProblem(null, ltypename, getQualifiedMessageArgs(reference),
                new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID },
                new Object[] { new Integer(IApiMarkerConstants.API_USAGE_MARKER_ID) }, lineNumber,
                IApiProblem.NO_CHARRANGE, IApiProblem.NO_CHARRANGE, getElementType(reference), getProblemKind(),
                getProblemFlags(reference));
    }

}