| /******************************************************************************* |
| * Copyright (c) 2008, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.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.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.SubMonitor; |
| 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.IApiBaseline; |
| 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 |
| */ |
| static 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) { |
| ApiPlugin.log(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) { |
| ApiPlugin.log(jme); |
| } |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * List of potential {@link IReference} problems |
| */ |
| private List<IReference> fPotentialProblems = new LinkedList<>(); |
| |
| /** |
| * 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; |
| } |
| |
| @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()); |
| IType typeInProject = Util.getTypeInSameJavaProject(type, lookupName, javaProject); |
| if (typeInProject != null) { |
| type =typeInProject; |
| } |
| 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 | 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()), |
| Integer.valueOf(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); |
| } |
| } |
| |
| @Override |
| public List<IApiProblem> createProblems(IProgressMonitor monitor) { |
| List<IReference> references = getRetainedReferences(); |
| if (references.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| List<IApiProblem> problems = new LinkedList<>(); |
| Iterator<IReference> iterator = references.iterator(); |
| SubMonitor loopMonitor = SubMonitor.convert(monitor, references.size()); |
| while (iterator.hasNext() && !monitor.isCanceled()) { |
| loopMonitor.split(1); |
| IReference reference = iterator.next(); |
| if (reference.getResolvedReference() == null) { |
| // unresolved reference ignore it |
| } else { |
| if (isProblem(reference, monitor)) { |
| IApiComponent component = reference.getMember().getApiComponent(); |
| try { |
| IApiProblem problem = null; |
| 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()); |
| checkIfDisposed(component, monitor); |
| } |
| } |
| } |
| } |
| return problems; |
| } |
| |
| /** |
| * Checks if given component is disposed or belongs to already disposed baseline |
| * - and if yes, cancels given monitor if the API analysis runs in a job |
| * |
| * @param component |
| * @param monitor |
| */ |
| public static void checkIfDisposed(IApiComponent component, IProgressMonitor monitor) { |
| if (component != null && !monitor.isCanceled() && ApiAnalysisBuilder.isRunningAsJob()) { |
| try { |
| if (component.isDisposed()) { |
| monitor.setCanceled(true); |
| return; |
| } |
| IApiBaseline baseline = component.getBaseline(); |
| if (baseline != null && baseline.isDisposed()) { |
| monitor.setCanceled(true); |
| } |
| } catch (CoreException e) { |
| monitor.setCanceled(true); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the resolved reference is a real problem. |
| * |
| * @param reference |
| * @param monitor |
| * @return whether a problem |
| */ |
| protected boolean isProblem(IReference reference, IProgressMonitor monitor) { |
| // 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) { |
| checkIfDisposed(reference.getMember().getApiComponent(), monitor); |
| 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 { |
| // If the exact same reference exist in another component id |
| IApiComponent apiComponent2 = apiComponent.getBaseline().getApiComponent(componentId); |
| String name = reference.getResolvedReference().getName(); |
| if (Util.getClassFile(new IApiComponent[] { |
| apiComponent2 }, name) != null) { |
| IApiComponent[] components = reference.getMember().getApiComponent().getBaseline().getPrerequisiteComponents(new IApiComponent[] { |
| reference.getMember().getApiComponent() }); |
| // and that component resides on prerequisite of component |
| // being analyzed - flag as error |
| for (IApiComponent iApiComponent : components) { |
| if (iApiComponent.getSymbolicName().equals(apiComponent2.getSymbolicName())) { |
| return true; |
| } |
| } |
| } |
| 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 (IMethod m : methods) { |
| 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 (IField loopField : fields) { |
| if (loopField.getElementName().equals(field.getName())) { |
| match = loopField; |
| 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.getJLSLatest(), 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; |
| for (IType t : type.getTypes()) { |
| if (t.getElementName().equals(apitype.getName())) { |
| match = t; |
| 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) { |
| // ignore |
| } |
| } |
| if (method == null) { |
| // look it up the hard way |
| ISourceRange range = jtype.getCompilationUnit().getSourceRange(); |
| ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); |
| 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 (IApiMethod m : methods) { |
| msig = m.getSignature(); |
| if (Signatures.getMethodName(m).equals(method.getElementName()) && Signatures.matchesSignatures(msig.replace('/', '.'), method.getSignature())) { |
| type.setEnclosingMethodInfo(m.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) { |
| start = 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[] { Integer.valueOf(IApiMarkerConstants.API_USAGE_MARKER_ID) }, lineNumber, IApiProblem.NO_CHARRANGE, IApiProblem.NO_CHARRANGE, getElementType(reference), getProblemKind(), getProblemFlags(reference)); |
| } |
| |
| /** |
| * @param reference |
| * @return the API problem if problem or null |
| * @throws CoreException |
| */ |
| public IApiProblem checkAndCreateProblem(IReference reference, IProgressMonitor monitor) throws CoreException { |
| if (isProblem(reference, monitor) == false) { |
| return null; |
| } |
| return createProblem(reference); |
| } |
| |
| } |