/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.matching;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.PackageReferenceMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Reference;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ImportBinding;
import org.eclipse.jdt.internal.compiler.lookup.MemberTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.util.Util;
public class PackageReferenceLocator extends PatternLocator {
protected PackageReferencePattern pattern;
// check that referenced type is actually defined in this package fragment
public static boolean isDeclaringPackageFragment(IPackageFragment packageFragment, ReferenceBinding typeBinding) {
char[] fileName= typeBinding.getFileName();
if (fileName != null) {
// retrieve the actual file name from the full path (sources are generally only containing it already)
fileName= CharOperation.replaceOnCopy(fileName, '/', '\\'); // ensure to not do any side effect on file name (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=136016)
fileName= CharOperation.lastSegment(fileName, '\\');
try {
switch (packageFragment.getKind()) {
case IPackageFragmentRoot.K_SOURCE:
if (!org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(fileName) || !packageFragment.getCompilationUnit(new String(fileName)).exists()) {
return false; // unit doesn't live in selected package
}
break;
case IPackageFragmentRoot.K_BINARY:
// if (Util.isJavaFileName(fileName)) { // binary with attached source
// int length = fileName.length;
// System.arraycopy(fileName, 0, fileName = new char[length], 0, length - 4); // copy all but extension
// System.arraycopy(SuffixConstants.SUFFIX_class, 0, fileName, length - 4, 4);
// }
if (!Util.isClassFileName(fileName) || !packageFragment.getClassFile(new String(fileName)).exists()) {
return false; // classfile doesn't live in selected package
}
break;
}
} catch (JavaModelException e) {
// unable to determine kind; tolerate this match
}
}
return true; // by default, do not eliminate
}
public PackageReferenceLocator(PackageReferencePattern pattern) {
super(pattern);
this.pattern= pattern;
}
public int match(Annotation node, MatchingNodeSet nodeSet) {
return match(node.type, nodeSet);
}
public int match(ASTNode node, MatchingNodeSet nodeSet) { // interested in ImportReference
if (!(node instanceof ImportReference))
return IMPOSSIBLE_MATCH;
return nodeSet.addMatch(node, matchLevel((ImportReference)node));
}
//public int match(ConstructorDeclaration node, MatchingNodeSet nodeSet) - SKIP IT
//public int match(Expression node, MatchingNodeSet nodeSet) - SKIP IT
//public int match(FieldDeclaration node, MatchingNodeSet nodeSet) - SKIP IT
//public int match(MethodDeclaration node, MatchingNodeSet nodeSet) - SKIP IT
//public int match(MessageSend node, MatchingNodeSet nodeSet) - SKIP IT
public int match(Reference node, MatchingNodeSet nodeSet) { // interested in QualifiedNameReference
if (!(node instanceof QualifiedNameReference))
return IMPOSSIBLE_MATCH;
return nodeSet.addMatch(node, matchLevelForTokens(((QualifiedNameReference)node).tokens));
}
//public int match(TypeDeclaration node, MatchingNodeSet nodeSet) - SKIP IT
public int match(TypeReference node, MatchingNodeSet nodeSet) { // interested in QualifiedTypeReference only
if (node instanceof JavadocSingleTypeReference) {
char[][] tokens= new char[][] { ((JavadocSingleTypeReference)node).token };
return nodeSet.addMatch(node, matchLevelForTokens(tokens));
}
if (!(node instanceof QualifiedTypeReference))
return IMPOSSIBLE_MATCH;
return nodeSet.addMatch(node, matchLevelForTokens(((QualifiedTypeReference)node).tokens));
}
protected int matchLevel(ImportReference importRef) {
return matchLevelForTokens(importRef.tokens);
}
protected int matchLevelForTokens(char[][] tokens) {
if (this.pattern.pkgName == null)
return ACCURATE_MATCH;
switch (this.matchMode) {
case SearchPattern.R_EXACT_MATCH:
case SearchPattern.R_PREFIX_MATCH:
if (CharOperation.prefixEquals(this.pattern.pkgName, CharOperation.concatWith(tokens, '.'), this.isCaseSensitive)) {
return POSSIBLE_MATCH;
}
break;
case SearchPattern.R_PATTERN_MATCH:
char[] patternName= this.pattern.pkgName[this.pattern.pkgName.length - 1] == '*'
? this.pattern.pkgName
: CharOperation.concat(this.pattern.pkgName, ".*".toCharArray()); //$NON-NLS-1$
if (CharOperation.match(patternName, CharOperation.concatWith(tokens, '.'), this.isCaseSensitive)) {
return POSSIBLE_MATCH;
}
break;
case SearchPattern.R_REGEXP_MATCH:
// TODO (frederic) implement regular expression match
break;
case SearchPattern.R_CAMELCASE_MATCH:
char[] packageName= CharOperation.concatWith(tokens, '.');
if (CharOperation.camelCaseMatch(this.pattern.pkgName, packageName, false)) {
return POSSIBLE_MATCH;
}
// only test case insensitive as CamelCase already verified prefix case sensitive
if (!this.isCaseSensitive && CharOperation.prefixEquals(this.pattern.pkgName, packageName, false)) {
return POSSIBLE_MATCH;
}
break;
case SearchPattern.R_CAMELCASE_SAME_PART_COUNT_MATCH:
if (CharOperation.camelCaseMatch(this.pattern.pkgName, CharOperation.concatWith(tokens, '.'), true)) {
return POSSIBLE_MATCH;
}
break;
}
return IMPOSSIBLE_MATCH;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.core.search.matching.PatternLocator#matchLevelAndReportImportRef(org.eclipse.jdt.internal.compiler.ast.ImportReference, org.eclipse.jdt.internal.compiler.lookup.Binding, org.eclipse.jdt.internal.core.search.matching.MatchLocator)
*/
protected void matchLevelAndReportImportRef(ImportReference importRef, Binding binding, MatchLocator locator) throws CoreException {
Binding refBinding= binding;
if (importRef.isStatic()) {
// for static import, binding can be a field binding or a member type binding
// verify that in this case binding is static and use declaring class for fields
if (binding instanceof FieldBinding) {
FieldBinding fieldBinding= (FieldBinding)binding;
if (!fieldBinding.isStatic())
return;
refBinding= fieldBinding.declaringClass;
} else if (binding instanceof MethodBinding) {
MethodBinding methodBinding= (MethodBinding)binding;
if (!methodBinding.isStatic())
return;
refBinding= methodBinding.declaringClass;
} else if (binding instanceof MemberTypeBinding) {
MemberTypeBinding memberBinding= (MemberTypeBinding)binding;
if (!memberBinding.isStatic())
return;
}
}
super.matchLevelAndReportImportRef(importRef, refBinding, locator);
}
protected void matchReportImportRef(ImportReference importRef, Binding binding, IJavaElement element, int accuracy, MatchLocator locator) throws CoreException {
if (binding == null) {
this.matchReportReference(importRef, element, null/*no binding*/, accuracy, locator);
} else {
if (locator.encloses(element)) {
long[] positions= importRef.sourcePositions;
int last= positions.length - 1;
if (binding instanceof ProblemReferenceBinding)
binding= ((ProblemReferenceBinding)binding).closestMatch();
if (binding instanceof ReferenceBinding) {
PackageBinding pkgBinding= ((ReferenceBinding)binding).fPackage;
if (pkgBinding != null)
last= pkgBinding.compoundName.length;
}
if (binding instanceof PackageBinding)
last= ((PackageBinding)binding).compoundName.length;
int start= (int)(positions[0] >>> 32);
int end= (int)positions[last - 1];
this.match= locator.newPackageReferenceMatch(element, accuracy, start, end - start + 1, importRef);
locator.report(this.match);
}
}
}
protected void matchReportReference(ASTNode reference, IJavaElement element, Binding elementBinding, int accuracy, MatchLocator locator) throws CoreException {
matchReportReference(reference, element, null, null, elementBinding, accuracy, locator);
}
protected void matchReportReference(ASTNode reference, IJavaElement element, IJavaElement localElement, IJavaElement[] otherElements, Binding elementBinding, int accuracy, MatchLocator locator)
throws CoreException {
long[] positions= null;
int last= -1;
if (reference instanceof ImportReference) {
ImportReference importRef= (ImportReference)reference;
positions= importRef.sourcePositions;
last= (importRef.bits & ASTNode.OnDemand) != 0 ? positions.length : positions.length - 1;
} else {
TypeBinding typeBinding= null;
if (reference instanceof QualifiedNameReference) {
QualifiedNameReference qNameRef= (QualifiedNameReference)reference;
positions= qNameRef.sourcePositions;
switch (qNameRef.bits & ASTNode.RestrictiveFlagMASK) {
case Binding.FIELD: // reading a field
typeBinding= qNameRef.actualReceiverType;
break;
case Binding.TYPE: //=============only type ==============
if (qNameRef.binding instanceof TypeBinding)
typeBinding= (TypeBinding)qNameRef.binding;
break;
case Binding.VARIABLE: //============unbound cases===========
case Binding.TYPE | Binding.VARIABLE:
Binding binding= qNameRef.binding;
if (binding instanceof TypeBinding) {
typeBinding= (TypeBinding)binding;
} else if (binding instanceof ProblemFieldBinding) {
typeBinding= qNameRef.actualReceiverType;
last= qNameRef.tokens.length - (qNameRef.otherBindings == null ? 2 : qNameRef.otherBindings.length + 2);
} else if (binding instanceof ProblemBinding) {
ProblemBinding pbBinding= (ProblemBinding)binding;
typeBinding= pbBinding.searchType;
last= CharOperation.occurencesOf('.', pbBinding.name);
}
break;
}
} else if (reference instanceof QualifiedTypeReference) {
QualifiedTypeReference qTypeRef= (QualifiedTypeReference)reference;
positions= qTypeRef.sourcePositions;
typeBinding= qTypeRef.resolvedType;
} else if (reference instanceof JavadocSingleTypeReference) {
JavadocSingleTypeReference jsTypeRef= (JavadocSingleTypeReference)reference;
positions= new long[1];
positions[0]= (((long)jsTypeRef.sourceStart) << 32) + jsTypeRef.sourceEnd;
typeBinding= jsTypeRef.resolvedType;
}
if (positions == null)
return;
if (typeBinding instanceof ArrayBinding)
typeBinding= ((ArrayBinding)typeBinding).leafComponentType;
if (typeBinding instanceof ProblemReferenceBinding)
typeBinding= ((ProblemReferenceBinding)typeBinding).closestMatch();
if (typeBinding instanceof ReferenceBinding) {
PackageBinding pkgBinding= ((ReferenceBinding)typeBinding).fPackage;
if (pkgBinding != null)
last= pkgBinding.compoundName.length;
}
// Do not report qualified references which are only enclosing type
// (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=91078)
ReferenceBinding enclosingType= typeBinding == null ? null : typeBinding.enclosingType();
if (enclosingType != null) {
int length= positions.length;
while (enclosingType != null && length > 0) {
length--;
enclosingType= enclosingType.enclosingType();
}
if (length <= 1)
return;
}
}
if (last == -1) {
last= this.pattern.segments.length;
}
if (last == 0)
return;
if (last > positions.length)
last= positions.length;
int sourceStart= (int)(positions[0] >>> 32);
int sourceEnd= ((int)positions[last - 1]);
PackageReferenceMatch packageReferenceMatch= locator.newPackageReferenceMatch(element, accuracy, sourceStart, sourceEnd - sourceStart + 1, reference);
packageReferenceMatch.setLocalElement(localElement);
this.match= packageReferenceMatch;
locator.report(this.match);
}
protected int referenceType() {
return IJavaElement.PACKAGE_FRAGMENT;
}
public int resolveLevel(ASTNode node) {
if (node instanceof JavadocQualifiedTypeReference) {
JavadocQualifiedTypeReference qualifRef= (JavadocQualifiedTypeReference)node;
if (qualifRef.packageBinding != null)
return resolveLevel(qualifRef.packageBinding);
return resolveLevel(qualifRef.resolvedType);
}
if (node instanceof JavadocSingleTypeReference) {
JavadocSingleTypeReference singleRef= (JavadocSingleTypeReference)node;
if (singleRef.packageBinding != null)
return resolveLevel(singleRef.packageBinding);
return IMPOSSIBLE_MATCH;
}
if (node instanceof QualifiedTypeReference)
return resolveLevel(((QualifiedTypeReference)node).resolvedType);
if (node instanceof QualifiedNameReference)
return this.resolveLevel((QualifiedNameReference)node);
// if (node instanceof ImportReference) - Not called when resolve is true, see MatchingNodeSet.reportMatching(unit)
return IMPOSSIBLE_MATCH;
}
public int resolveLevel(Binding binding) {
if (binding == null)
return INACCURATE_MATCH;
char[][] compoundName= null;
if (binding instanceof ImportBinding) {
compoundName= ((ImportBinding)binding).compoundName;
} else if (binding instanceof PackageBinding) {
compoundName= ((PackageBinding)binding).compoundName;
} else {
if (binding instanceof ArrayBinding)
binding= ((ArrayBinding)binding).leafComponentType;
if (binding instanceof ProblemReferenceBinding)
binding= ((ProblemReferenceBinding)binding).closestMatch();
if (binding == null)
return INACCURATE_MATCH;
if (binding instanceof ReferenceBinding) {
PackageBinding pkgBinding= ((ReferenceBinding)binding).fPackage;
if (pkgBinding == null)
return INACCURATE_MATCH;
compoundName= pkgBinding.compoundName;
}
}
if (compoundName != null && matchesName(this.pattern.pkgName, CharOperation.concatWith(compoundName, '.'))) {
if (this.pattern.focus instanceof IPackageFragment && binding instanceof ReferenceBinding) {
// check that type is located inside this instance of a package fragment
if (!isDeclaringPackageFragment((IPackageFragment)this.pattern.focus, (ReferenceBinding)binding))
return IMPOSSIBLE_MATCH;
}
return ACCURATE_MATCH;
}
return IMPOSSIBLE_MATCH;
}
protected int resolveLevel(QualifiedNameReference qNameRef) {
TypeBinding typeBinding= null;
switch (qNameRef.bits & ASTNode.RestrictiveFlagMASK) {
case Binding.FIELD: // reading a field
if (qNameRef.tokens.length < (qNameRef.otherBindings == null ? 3 : qNameRef.otherBindings.length + 3))
return IMPOSSIBLE_MATCH; // must be at least p1.A.x
typeBinding= qNameRef.actualReceiverType;
break;
case Binding.LOCAL: // reading a local variable
return IMPOSSIBLE_MATCH; // no package match in it
case Binding.TYPE: //=============only type ==============
if (qNameRef.binding instanceof TypeBinding)
typeBinding= (TypeBinding)qNameRef.binding;
break;
/*
* Handling of unbound qualified name references. The match may reside in the resolved fragment,
* which is recorded inside the problem binding, along with the portion of the name until it became a problem.
*/
case Binding.VARIABLE: //============unbound cases===========
case Binding.TYPE | Binding.VARIABLE:
Binding binding= qNameRef.binding;
if (binding instanceof ProblemReferenceBinding) {
typeBinding= (TypeBinding)binding;
} else if (binding instanceof ProblemFieldBinding) {
if (qNameRef.tokens.length < (qNameRef.otherBindings == null ? 3 : qNameRef.otherBindings.length + 3))
return IMPOSSIBLE_MATCH; // must be at least p1.A.x
typeBinding= qNameRef.actualReceiverType;
} else if (binding instanceof ProblemBinding) {
ProblemBinding pbBinding= (ProblemBinding)binding;
if (CharOperation.occurencesOf('.', pbBinding.name) <= 0) // index of last bound token is one before the pb token
return INACCURATE_MATCH;
typeBinding= pbBinding.searchType;
}
break;
}
return resolveLevel(typeBinding);
}
public String toString() {
return "Locator for " + this.pattern.toString(); //$NON-NLS-1$
}
}