package org.eclipse.jdt.internal.codeassist; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.Locale; | |
import org.eclipse.jdt.internal.compiler.*; | |
import org.eclipse.jdt.internal.compiler.env.*; | |
import org.eclipse.jdt.internal.codeassist.impl.*; | |
import org.eclipse.jdt.internal.codeassist.complete.*; | |
import org.eclipse.jdt.internal.compiler.ast.*; | |
import org.eclipse.jdt.internal.compiler.lookup.*; | |
import org.eclipse.jdt.internal.compiler.parser.*; | |
import org.eclipse.jdt.internal.compiler.problem.*; | |
import org.eclipse.jdt.internal.compiler.util.*; | |
import org.eclipse.jdt.internal.compiler.impl.*; | |
/** | |
* This class is the entry point for source completions. | |
* It contains two public APIs used to call CodeAssist on a given source with | |
* a given environment, assisting position and storage (and possibly options). | |
*/ | |
public final class CompletionEngine extends Engine implements ISearchRequestor, TypeConstants { | |
CompletionParser parser; | |
ISearchableNameEnvironment nameEnvironment; | |
ICompletionRequestor requestor; | |
CompilationUnitScope unitScope; | |
char[] source; | |
boolean resolvingImports = false; | |
boolean insideQualifiedReference = false; | |
int startPosition, endPosition; | |
HashtableOfObject knownPkgs = new HashtableOfObject(10); | |
/* | |
static final char[][] mainDeclarations = | |
new char[][] { | |
"package".toCharArray(), | |
"import".toCharArray(), | |
"abstract".toCharArray(), | |
"final".toCharArray(), | |
"public".toCharArray(), | |
"class".toCharArray(), | |
"interface".toCharArray()}; | |
static final char[][] modifiers = // may want field, method, type & member type modifiers | |
new char[][] { | |
"abstract".toCharArray(), | |
"final".toCharArray(), | |
"native".toCharArray(), | |
"public".toCharArray(), | |
"protected".toCharArray(), | |
"private".toCharArray(), | |
"static".toCharArray(), | |
"strictfp".toCharArray(), | |
"synchronized".toCharArray(), | |
"transient".toCharArray(), | |
"volatile".toCharArray()}; | |
*/ | |
static final char[][] baseTypes = | |
new char[][] { | |
"boolean"/*nonNLS*/.toCharArray(), | |
"byte"/*nonNLS*/.toCharArray(), | |
"char"/*nonNLS*/.toCharArray(), | |
"double"/*nonNLS*/.toCharArray(), | |
"float"/*nonNLS*/.toCharArray(), | |
"int"/*nonNLS*/.toCharArray(), | |
"long"/*nonNLS*/.toCharArray(), | |
"short"/*nonNLS*/.toCharArray(), | |
"void"/*nonNLS*/.toCharArray()}; | |
static final char[] classField = "class"/*nonNLS*/.toCharArray(); | |
static final char[] lengthField = "length"/*nonNLS*/.toCharArray(); | |
/** | |
* The CompletionEngine is responsible for computing source completions. | |
* | |
* It requires a searchable name environment, which supports some | |
* specific search APIs, and a requestor to feed back the results to a UI. | |
* | |
* @param environment com.ibm.codeassist.java.api.ISearchableNameEnvironment | |
* used to resolve type/package references and search for types/packages | |
* based on partial names. | |
* | |
* @param requestor com.ibm.codeassist.java.api.ICompletionRequestor | |
* since the engine might produce answers of various forms, the engine | |
* is associated with a requestor able to accept all possible completions. | |
* | |
* @param options com.ibm.compiler.java.api.ConfigurableOptions | |
* set of options used to configure the code assist engine. | |
*/ | |
public CompletionEngine( | |
ISearchableNameEnvironment nameEnvironment, ICompletionRequestor requestor, ConfigurableOption[] settings) { | |
this.requestor = requestor; | |
this.nameEnvironment = nameEnvironment; | |
CompilerOptions options = new CompilerOptions(settings); | |
ProblemReporter problemReporter = | |
new ProblemReporter( | |
DefaultErrorHandlingPolicies.proceedWithAllProblems(), | |
options, | |
new DefaultProblemFactory(Locale.getDefault())) { | |
public void record(IProblem problem, CompilationResult unitResult) { | |
if (problem.getID() != ProblemIrritants.UnmatchedBracket) { | |
unitResult.record(problem); | |
CompletionEngine.this.requestor.acceptError(problem); | |
} | |
} | |
}; | |
this.parser = new CompletionParser(problemReporter); | |
this.lookupEnvironment = new LookupEnvironment(this, options, problemReporter, nameEnvironment); | |
} | |
/** | |
* One result of the search consists of a new class. | |
* | |
* NOTE - All package and type names are presented in their readable form: | |
* Package names are in the form "a.b.c". | |
* Nested type names are in the qualified form "A.M". | |
* The default package is represented by an empty array. | |
*/ | |
public void acceptClass(char[] packageName, char[] className, int modifiers) { | |
char[] completionName = CharOperation.concat(packageName, className, '.'); | |
if (resolvingImports) { | |
completionName = CharOperation.concat(completionName, new char[] {';'}); | |
} else if (!insideQualifiedReference) { | |
if (mustQualifyType(CharOperation.splitOn('.', packageName), completionName)) { | |
if (packageName == null || packageName.length == 0) | |
if (unitScope != null && unitScope.fPackage.compoundName != NoCharChar) | |
return; // ignore types from the default package from outside it | |
} else { | |
completionName = className; | |
} | |
} | |
requestor.acceptClass( | |
packageName, | |
className, | |
completionName, | |
modifiers, | |
startPosition, | |
endPosition); | |
} | |
/** | |
* One result of the search consists of a new interface. | |
* | |
* NOTE - All package and type names are presented in their readable form: | |
* Package names are in the form "a.b.c". | |
* Nested type names are in the qualified form "A.I". | |
* The default package is represented by an empty array. | |
*/ | |
public void acceptInterface(char[] packageName, char[] interfaceName, int modifiers) { | |
char[] completionName = CharOperation.concat(packageName, interfaceName, '.'); | |
if (resolvingImports) { | |
completionName = CharOperation.concat(completionName, new char[] {';'}); | |
} else if (!insideQualifiedReference) { | |
if (mustQualifyType(CharOperation.splitOn('.', packageName), completionName)) { | |
if (packageName == null || packageName.length == 0) | |
if (unitScope != null && unitScope.fPackage.compoundName != NoCharChar) | |
return; // ignore types from the default package from outside it | |
} else { | |
completionName = interfaceName; | |
} | |
} | |
requestor.acceptInterface( | |
packageName, | |
interfaceName, | |
completionName, | |
modifiers, | |
startPosition, | |
endPosition); | |
} | |
/** | |
* One result of the search consists of a new package. | |
* | |
* NOTE - All package names are presented in their readable form: | |
* Package names are in the form "a.b.c". | |
* The default package is represented by an empty array. | |
*/ | |
public void acceptPackage(char[] packageName) { | |
if (this.knownPkgs.containsKey(packageName)) return; | |
this.knownPkgs.put(packageName, this); | |
requestor.acceptPackage( | |
packageName, | |
resolvingImports ? CharOperation.concat(packageName, new char[] {'.', '*', ';'}) : packageName, | |
startPosition, | |
endPosition); | |
} | |
/** | |
* One result of the search consists of a new type. | |
* | |
* NOTE - All package and type names are presented in their readable form: | |
* Package names are in the form "a.b.c". | |
* Nested type names are in the qualified form "A.M". | |
* The default package is represented by an empty array. | |
*/ | |
public void acceptType(char[] packageName, char[] typeName) { | |
char[] completionName = CharOperation.concat(packageName, typeName, '.'); | |
if (resolvingImports) { | |
completionName = CharOperation.concat(completionName, new char[] {';'}); | |
} else if (!insideQualifiedReference) { | |
if (mustQualifyType(CharOperation.splitOn('.', packageName), completionName)) { | |
if (packageName == null || packageName.length == 0) | |
if (unitScope != null && unitScope.fPackage.compoundName != NoCharChar) | |
return; // ignore types from the default package from outside it | |
} else { | |
completionName = typeName; | |
} | |
} | |
requestor.acceptType( | |
packageName, | |
typeName, | |
completionName, | |
startPosition, | |
endPosition); | |
} | |
private void complete(AstNode astNode, Binding qualifiedBinding, Scope scope) { | |
setSourceRange(astNode.sourceStart, astNode.sourceEnd); // defaults... some nodes will change these | |
if (astNode instanceof CompletionOnFieldType) { | |
CompletionOnSingleTypeReference type = | |
(CompletionOnSingleTypeReference) ((CompletionOnFieldType) astNode).type; | |
char[] token = type.token; | |
setSourceRange(type.sourceStart, type.sourceEnd); | |
// findKeywords(token, modifiers, scope); // could be the start of a field, method or member type | |
findTypesAndPackages(token, scope); | |
} else if (astNode instanceof CompletionOnSingleNameReference) { | |
char[] token = ((CompletionOnSingleNameReference) astNode).token; | |
findVariablesAndMethods(token, scope); | |
findTypesAndPackages(token, scope); // can be the start of a qualified type name | |
} else if (astNode instanceof CompletionOnSingleTypeReference) { | |
char[] token = ((CompletionOnSingleTypeReference) astNode).token; | |
if (qualifiedBinding == null) | |
findTypesAndPackages(token, scope); // can be the start of a qualified type name | |
else | |
findMemberTypes(token, (ReferenceBinding) qualifiedBinding, scope); | |
} else if (astNode instanceof CompletionOnQualifiedNameReference) { | |
insideQualifiedReference = true; | |
CompletionOnQualifiedNameReference ref = (CompletionOnQualifiedNameReference) astNode; | |
char[] token = ref.completionIdentifier; | |
long completionPosition = ref.sourcePositions[ref.sourcePositions.length - 1]; | |
if (qualifiedBinding instanceof VariableBinding) { | |
setSourceRange((int) (completionPosition >>> 32), (int) completionPosition); | |
TypeBinding receiverType = ((VariableBinding) qualifiedBinding).type; | |
if (receiverType != null) | |
findFieldsAndMethods(token, receiverType, scope); | |
} else if (qualifiedBinding instanceof ReferenceBinding) { | |
ReferenceBinding receiverType = (ReferenceBinding) qualifiedBinding; | |
setSourceRange((int) (completionPosition >>> 32), (int) completionPosition); | |
findMemberTypes(token, receiverType, scope); | |
findClassField(token, (TypeBinding) qualifiedBinding); | |
findFields(token, receiverType, scope, new ObjectVector(), true); | |
findMethods(token, null, receiverType, scope, new ObjectVector(), true, false); | |
} else if (qualifiedBinding instanceof PackageBinding) { | |
setSourceRange(astNode.sourceStart, (int) completionPosition); // replace to the end of the completion identifier | |
findTypesAndSubpackages(token, (PackageBinding) qualifiedBinding); | |
} | |
} else if (astNode instanceof CompletionOnQualifiedTypeReference) { | |
insideQualifiedReference = true; | |
CompletionOnQualifiedTypeReference ref = (CompletionOnQualifiedTypeReference) astNode; | |
char[] token = ref.completionIdentifier; | |
long completionPosition = ref.sourcePositions[ref.tokens.length]; // get the source positions of the completion identifier | |
if (qualifiedBinding instanceof ReferenceBinding) { | |
setSourceRange((int) (completionPosition >>> 32), (int) completionPosition); | |
findMemberTypes(token, (ReferenceBinding) qualifiedBinding, scope); | |
} else if (qualifiedBinding instanceof PackageBinding) { | |
setSourceRange(astNode.sourceStart, (int) completionPosition); // replace to the end of the completion identifier | |
findTypesAndSubpackages(token, (PackageBinding) qualifiedBinding); | |
} | |
} else if (astNode instanceof CompletionOnMemberAccess) { | |
CompletionOnMemberAccess access = (CompletionOnMemberAccess) astNode; | |
long completionPosition = access.nameSourcePosition; | |
setSourceRange((int) (completionPosition >>> 32), (int) completionPosition); | |
findFieldsAndMethods(access.token, (TypeBinding) qualifiedBinding, scope); | |
} else if (astNode instanceof CompletionOnMessageSend) { | |
CompletionOnMessageSend messageSend = (CompletionOnMessageSend) astNode; | |
TypeBinding[] argTypes = computeTypes(messageSend.arguments, (BlockScope) scope); | |
if (qualifiedBinding == null) | |
findMessageSends(messageSend.selector, argTypes, scope); | |
else | |
findMethods(messageSend.selector, argTypes, (ReferenceBinding) qualifiedBinding, scope, new ObjectVector(), false, true); | |
} else if (astNode instanceof CompletionOnExplicitConstructorCall) { | |
CompletionOnExplicitConstructorCall constructorCall = (CompletionOnExplicitConstructorCall) astNode; | |
TypeBinding[] argTypes = computeTypes(constructorCall.arguments, (BlockScope) scope); | |
findConstructors((ReferenceBinding) qualifiedBinding, argTypes, scope); | |
} else if (astNode instanceof CompletionOnQualifiedAllocationExpression) { | |
CompletionOnQualifiedAllocationExpression allocExpression = (CompletionOnQualifiedAllocationExpression) astNode; | |
TypeBinding[] argTypes = computeTypes(allocExpression.arguments, (BlockScope) scope); | |
findConstructors((ReferenceBinding) qualifiedBinding, argTypes, scope); | |
} else if (astNode instanceof CompletionOnClassLiteralAccess) { | |
char[] token = ((CompletionOnClassLiteralAccess) astNode).completionIdentifier; | |
findClassField(token, (TypeBinding) qualifiedBinding); | |
} | |
} | |
/** | |
* Ask the engine to compute a completion at the specified position | |
* of the given compilation unit. | |
* | |
* @return void | |
* completion results are answered through a requestor. | |
* | |
* @param unit com.ibm.compiler.java.api.env.ICompilationUnit | |
* the source of the current compilation unit. | |
* | |
* @param completionPosition int | |
* a position in the source where the completion is taking place. | |
* This position is relative to the source provided. | |
*/ | |
public void complete(ICompilationUnit sourceUnit, int completionPosition) { | |
try { | |
int actualCompletionPosition = completionPosition - 1; // for now until we can change the UI. | |
CompilationResult result = new CompilationResult(sourceUnit, 1, 1); | |
CompilationUnitDeclaration parsedUnit = parser.dietParse(sourceUnit, result, actualCompletionPosition); | |
// boolean completionNodeFound = false; | |
if (parsedUnit != null) { | |
// scan the package & import statements first | |
if (parsedUnit.currentPackage instanceof CompletionOnPackageReference) { | |
findPackages((CompletionOnPackageReference) parsedUnit.currentPackage); | |
return; | |
} | |
ImportReference[] imports = parsedUnit.imports; | |
if (imports != null) { | |
for (int i = 0, length = imports.length; i < length; i++) { | |
ImportReference importReference = imports[i]; | |
if (importReference instanceof CompletionOnImportReference) { | |
findImports((CompletionOnImportReference) importReference); | |
return; | |
} | |
} | |
} | |
if (parsedUnit.types != null) { | |
try { | |
lookupEnvironment.buildTypeBindings(parsedUnit); | |
if ((unitScope = parsedUnit.scope) != null) { | |
source = sourceUnit.getContents(); | |
lookupEnvironment.completeTypeBindings(parsedUnit, true); | |
parsedUnit.scope.faultInTypes(); | |
parseMethod(parsedUnit, actualCompletionPosition); | |
parsedUnit.resolve(); | |
} | |
} catch (CompletionNodeFound e) { | |
// completionNodeFound = true; | |
if (e.astNode != null) // if null then we found a problem in the completion node | |
complete(e.astNode, e.qualifiedBinding, e.scope); | |
} | |
} | |
} | |
/* Ignore package, import, class & interface keywords for now... | |
if (!completionNodeFound) { | |
if (parsedUnit == null || parsedUnit.types == null) { | |
// this is not good enough... can still be trying to define a second type | |
CompletionScanner scanner = (CompletionScanner) parser.scanner; | |
setSourceRange(scanner.completedIdentifierStart, scanner.completedIdentifierEnd); | |
findKeywords(scanner.completionIdentifier, mainDeclarations, null); | |
} | |
// currently have no way to know if extends/implements are possible keywords | |
} | |
*/ } catch (IndexOutOfBoundsException e) { // work-around internal failure - 1GEMF6D | |
} catch (InvalidCursorLocation e) { // may eventually report a usefull error | |
} catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object | |
} finally { | |
reset(); | |
} | |
} | |
private TypeBinding[] computeTypes(Expression[] arguments, BlockScope scope) { | |
if (arguments == null) return null; | |
int argsLength = arguments.length; | |
TypeBinding[] argTypes = new TypeBinding[argsLength]; | |
for (int a = argsLength; --a >= 0;) | |
argTypes[a] = arguments[a].resolveType(scope); | |
return argTypes; | |
} | |
private void findClassField(char[] token, TypeBinding receiverType) { | |
if (token == null) return; | |
if (token.length <= classField.length && CharOperation.prefixEquals(token, classField, false /* ignore case */)) | |
requestor.acceptField( | |
NoChar, | |
NoChar, | |
classField, | |
NoChar, | |
NoChar, | |
classField, | |
CompilerModifiers.AccStatic | CompilerModifiers.AccPublic, | |
startPosition, | |
endPosition); | |
} | |
private void findConstructors(ReferenceBinding currentType, TypeBinding[] argTypes, Scope scope) { | |
// No visibility checks can be performed without the scope & invocationSite | |
MethodBinding[] methods = currentType.methods(); | |
int minArgLength = argTypes == null ? 0 : argTypes.length; | |
next : for (int f = methods.length; --f >= 0;) { | |
MethodBinding constructor = methods[f]; | |
if (constructor.isConstructor()) { | |
TypeBinding[] parameters = constructor.parameters; | |
int paramLength = parameters.length; | |
if (minArgLength > paramLength) continue next; | |
for (int a = minArgLength; --a >= 0;) | |
if (argTypes[a] != null) // can be null if it could not be resolved properly | |
if (!scope.areTypesCompatible(argTypes[a], constructor.parameters[a])) continue next; | |
char[][] parameterPackageNames = new char[paramLength][]; | |
char[][] parameterTypeNames = new char[paramLength][]; | |
for (int i = 0; i < paramLength; i++) { | |
TypeBinding type = parameters[i]; | |
parameterPackageNames[i] = type.qualifiedPackageName(); | |
parameterTypeNames[i] = type.qualifiedSourceName(); | |
} | |
char[] completion = TypeConstants.NoChar; // nothing to insert - do not want to replace the existing selector & arguments | |
if (source == null || source.length <= endPosition || source[endPosition] != ')') | |
completion = new char[] {')'}; | |
requestor.acceptMethod( | |
currentType.qualifiedPackageName(), | |
currentType.qualifiedSourceName(), | |
currentType.sourceName(), | |
parameterPackageNames, | |
parameterTypeNames, | |
TypeConstants.NoChar, | |
TypeConstants.NoChar, | |
completion, | |
constructor.modifiers, | |
endPosition, | |
endPosition); | |
} | |
} | |
} | |
// Helper method for findFields(char[], ReferenceBinding, Scope, ObjectVector, boolean) | |
private void findFields( | |
char[] fieldName, | |
FieldBinding[] fields, | |
Scope scope, | |
ObjectVector fieldsFound, | |
boolean onlyStaticFields) { | |
// Inherited fields which are hidden by subclasses are filtered out | |
// No visibility checks can be performed without the scope & invocationSite | |
int fieldLength = fieldName.length; | |
next : for (int f = fields.length; --f >= 0;) { | |
FieldBinding field = fields[f]; | |
if (onlyStaticFields && !field.isStatic()) continue next; | |
if (fieldLength > field.name.length) continue next; | |
if (!CharOperation.prefixEquals(fieldName, field.name, false /* ignore case */)) continue next; | |
for (int i = fieldsFound.size; --i >= 0;) { | |
FieldBinding otherField = (FieldBinding) fieldsFound.elementAt(i); | |
if (field == otherField) continue next; | |
if (CharOperation.equals(field.name, otherField.name, true)) { | |
if (field.declaringClass.isSuperclassOf(otherField.declaringClass)) continue next; | |
if (otherField.declaringClass.isInterface()) | |
if (field.declaringClass.implementsInterface(otherField.declaringClass, true)) continue next; | |
} | |
} | |
fieldsFound.add(field); | |
requestor.acceptField( | |
field.declaringClass.qualifiedPackageName(), | |
field.declaringClass.qualifiedSourceName(), | |
field.name, | |
field.type.qualifiedPackageName(), | |
field.type.qualifiedSourceName(), | |
field.name, // may include some qualification to resolve ambiguities | |
field.modifiers, | |
startPosition, | |
endPosition); | |
} | |
} | |
private void findFields( | |
char[] fieldName, | |
ReferenceBinding receiverType, | |
Scope scope, | |
ObjectVector fieldsFound, | |
boolean onlyStaticFields) { | |
if (fieldName == null) return; | |
ReferenceBinding currentType = receiverType; | |
ReferenceBinding[][] interfacesToVisit = null; | |
int lastPosition = -1; | |
do { | |
ReferenceBinding[] itsInterfaces = currentType.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
if (interfacesToVisit == null) | |
interfacesToVisit = new ReferenceBinding[5][]; | |
if (++lastPosition == interfacesToVisit.length) | |
System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
} | |
findFields(fieldName, currentType.fields(), scope, fieldsFound, onlyStaticFields); | |
currentType = currentType.superclass(); | |
} while (currentType != null); | |
if (interfacesToVisit != null) { | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) { | |
ReferenceBinding anInterface = interfaces[j]; | |
if ((anInterface.tagBits & TagBits.InterfaceVisited) == 0) { // if interface as not already been visited | |
anInterface.tagBits |= TagBits.InterfaceVisited; | |
findFields(fieldName, anInterface.fields(), scope, fieldsFound, onlyStaticFields); | |
ReferenceBinding[] itsInterfaces = anInterface.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
if (++lastPosition == interfacesToVisit.length) | |
System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
} | |
} | |
} | |
} | |
// bit reinitialization | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) | |
interfaces[j].tagBits &= ~TagBits.InterfaceVisited; | |
} | |
} | |
} | |
private void findFieldsAndMethods(char[] token, TypeBinding receiverType, Scope scope) { | |
if (token == null) return; | |
if (receiverType.isBaseType()) return; // nothing else is possible with base types | |
if (receiverType.isArrayType()) { | |
if (token.length <= lengthField.length && CharOperation.prefixEquals(token, lengthField, false /* ignore case */)) | |
requestor.acceptField( | |
NoChar, | |
NoChar, | |
lengthField, | |
NoChar, | |
NoChar, | |
lengthField, | |
CompilerModifiers.AccPublic, | |
startPosition, | |
endPosition); | |
receiverType = scope.getJavaLangObject(); | |
} | |
findFields(token, (ReferenceBinding) receiverType, scope, new ObjectVector(), false); | |
findMethods(token, null, (ReferenceBinding) receiverType, scope, new ObjectVector(), false, false); | |
} | |
private void findImports(CompletionOnImportReference importReference) { | |
char[] importName = CharOperation.concatWith(importReference.tokens, '.'); | |
if (importName.length == 0) return; | |
resolvingImports = true; | |
setSourceRange(importReference.sourceStart, importReference.declarationSourceEnd); // want to replace the existing .*; | |
nameEnvironment.findPackages(importName, this); | |
nameEnvironment.findTypes(importName, this); | |
} | |
// what about onDemand types? Ignore them since it does not happen! | |
// import p1.p2.A.*; | |
private void findKeywords(char[] keyword, char[][] choices, Scope scope) { | |
int length = keyword.length; | |
if (length > 0) | |
for (int i = 0; i < choices.length; i++) | |
if (length <= choices[i].length && CharOperation.prefixEquals(keyword, choices[i], false /* ignore case */)) | |
requestor.acceptKeyword(choices[i], startPosition, endPosition); | |
} | |
// Helper method for findMemberTypes(char[], ReferenceBinding, Scope) | |
private void findMemberTypes(char[] typeName, ReferenceBinding[] memberTypes, ObjectVector typesFound) { | |
// Inherited member types which are hidden by subclasses are filtered out | |
// No visibility checks can be performed without the scope & invocationSite | |
int typeLength = typeName.length; | |
next : for (int m = memberTypes.length; --m >= 0;) { | |
ReferenceBinding memberType = memberTypes[m]; | |
// if (!wantClasses && memberType.isClass()) continue next; | |
// if (!wantInterfaces && memberType.isInterface()) continue next; | |
if (typeLength > memberType.sourceName.length) continue next; | |
if (!CharOperation.prefixEquals(typeName, memberType.sourceName, false /* ignore case */)) continue next; | |
for (int i = typesFound.size; --i >= 0;) { | |
ReferenceBinding otherType = (ReferenceBinding) typesFound.elementAt(i); | |
if (memberType == otherType) continue next; | |
if (CharOperation.equals(memberType.sourceName, otherType.sourceName, true)) { | |
if (memberType.enclosingType().isSuperclassOf(otherType.enclosingType())) continue next; | |
if (otherType.enclosingType().isInterface()) | |
if (memberType.enclosingType().implementsInterface(otherType.enclosingType(), true)) continue next; | |
} | |
} | |
typesFound.add(memberType); | |
if (memberType.isClass()) | |
requestor.acceptClass( | |
memberType.qualifiedPackageName(), | |
memberType.qualifiedSourceName(), | |
memberType.sourceName(), | |
memberType.modifiers, | |
startPosition, | |
endPosition); | |
else | |
requestor.acceptInterface( | |
memberType.qualifiedPackageName(), | |
memberType.qualifiedSourceName(), | |
memberType.sourceName(), | |
memberType.modifiers, | |
startPosition, | |
endPosition); | |
} | |
} | |
private void findMemberTypes(char[] typeName, ReferenceBinding currentType, Scope scope) { | |
if (typeName == null) return; | |
if (currentType.superInterfaces() == null) return; // we're trying to find a supertype | |
ObjectVector typesFound = new ObjectVector(); | |
if (insideQualifiedReference || typeName.length == 0) { // do not search up the hierarchy | |
findMemberTypes(typeName, currentType.memberTypes(), typesFound); | |
return; | |
} | |
ReferenceBinding[][] interfacesToVisit = null; | |
int lastPosition = -1; | |
do { | |
ReferenceBinding[] itsInterfaces = currentType.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
if (interfacesToVisit == null) | |
interfacesToVisit = new ReferenceBinding[5][]; | |
if (++lastPosition == interfacesToVisit.length) | |
System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
} | |
findMemberTypes(typeName, currentType.memberTypes(), typesFound); | |
currentType = currentType.superclass(); | |
} while (currentType != null); | |
if (interfacesToVisit != null) { | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) { | |
ReferenceBinding anInterface = interfaces[j]; | |
if ((anInterface.tagBits & TagBits.InterfaceVisited) == 0) { // if interface as not already been visited | |
anInterface.tagBits |= TagBits.InterfaceVisited; | |
findMemberTypes(typeName, anInterface.memberTypes(), typesFound); | |
ReferenceBinding[] itsInterfaces = anInterface.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
if (++lastPosition == interfacesToVisit.length) | |
System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
} | |
} | |
} | |
} | |
// bit reinitialization | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) | |
interfaces[j].tagBits &= ~TagBits.InterfaceVisited; | |
} | |
} | |
} | |
private void findMessageSends(char[] token, TypeBinding[] argTypes, Scope scope) { | |
if (token == null) return; | |
boolean staticsOnly = false; // need to know if we're in a static context (or inside a constructor) | |
int tokenLength = token.length; | |
ObjectVector methodsFound = new ObjectVector(); | |
done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found | |
switch (scope.kind) { | |
case Scope.METHOD_SCOPE : | |
// handle the error case inside an explicit constructor call (see MethodScope>>findField) | |
MethodScope methodScope = (MethodScope) scope; | |
staticsOnly |= methodScope.isStatic | methodScope.isConstructorCall; | |
break; | |
case Scope.CLASS_SCOPE : | |
ClassScope classScope = (ClassScope) scope; | |
SourceTypeBinding enclosingType = classScope.referenceContext.binding; | |
findMethods(token, argTypes, enclosingType, classScope, methodsFound, staticsOnly, true); | |
staticsOnly |= enclosingType.isStatic(); | |
break; | |
case Scope.COMPILATION_UNIT_SCOPE : | |
break done; | |
} | |
scope = scope.parent; | |
} | |
} | |
// Helper method for findMethods(char[], TypeBinding[], ReferenceBinding, Scope, ObjectVector, boolean, boolean) | |
private void findMethods( | |
char[] methodName, | |
TypeBinding[] argTypes, | |
MethodBinding[] methods, | |
Scope scope, | |
ObjectVector methodsFound, | |
// boolean noVoidReturnType, how do you know? | |
boolean onlyStaticMethods, | |
boolean exactMatch) { | |
// Inherited methods which are hidden by subclasses are filtered out | |
// No visibility checks can be performed without the scope & invocationSite | |
int methodLength = methodName.length; | |
int minArgLength = argTypes == null ? 0 : argTypes.length; | |
next : for (int f = methods.length; --f >= 0;) { | |
MethodBinding method = methods[f]; | |
if (method.isConstructor()) continue next; | |
// if (noVoidReturnType && method.returnType == BaseTypes.VoidBinding) continue next; | |
if (onlyStaticMethods && !method.isStatic()) continue next; | |
if (exactMatch) { | |
if (!CharOperation.equals(methodName, method.selector, false /* ignore case */)) continue next; | |
} else { | |
if (methodLength > method.selector.length) continue next; | |
if (!CharOperation.prefixEquals(methodName, method.selector, false /* ignore case */)) continue next; | |
} | |
if (minArgLength > method.parameters.length) continue next; | |
for (int a = minArgLength; --a >= 0;) | |
if (argTypes[a] != null) // can be null if it could not be resolved properly | |
if (!scope.areTypesCompatible(argTypes[a], method.parameters[a])) continue next; | |
for (int i = methodsFound.size; --i >= 0;) { | |
MethodBinding otherMethod = (MethodBinding) methodsFound.elementAt(i); | |
if (method == otherMethod) continue next; | |
if (CharOperation.equals(method.selector, otherMethod.selector, true) && method.areParametersEqual(otherMethod)) { | |
if (method.declaringClass.isSuperclassOf(otherMethod.declaringClass)) continue next; | |
if (otherMethod.declaringClass.isInterface()) | |
if (method.declaringClass.implementsInterface(otherMethod.declaringClass, true)) continue next; | |
} | |
} | |
methodsFound.add(method); | |
int length = method.parameters.length; | |
char[][] parameterPackageNames = new char[length][]; | |
char[][] parameterTypeNames = new char[length][]; | |
for (int i = 0; i < length; i++) { | |
TypeBinding type = method.parameters[i]; | |
parameterPackageNames[i] = type.qualifiedPackageName(); | |
parameterTypeNames[i] = type.qualifiedSourceName(); | |
} | |
char[] completion = TypeConstants.NoChar; // nothing to insert - do not want to replace the existing selector & arguments | |
if (!exactMatch) { | |
if (source != null && source.length > endPosition && source[endPosition] == '(') | |
completion = method.selector; | |
else | |
completion = CharOperation.concat(method.selector, new char[] {'(', ')'}); | |
} | |
requestor.acceptMethod( | |
method.declaringClass.qualifiedPackageName(), | |
method.declaringClass.qualifiedSourceName(), | |
method.selector, | |
parameterPackageNames, | |
parameterTypeNames, | |
method.returnType.qualifiedPackageName(), | |
method.returnType.qualifiedSourceName(), | |
completion, | |
method.modifiers, | |
startPosition, | |
endPosition); | |
} | |
} | |
private void findMethods( | |
char[] selector, | |
TypeBinding[] argTypes, | |
ReferenceBinding receiverType, | |
Scope scope, | |
ObjectVector methodsFound, | |
boolean onlyStaticMethods, | |
boolean exactMatch) { | |
if (selector == null) return; | |
ReferenceBinding currentType = receiverType; | |
if (currentType.isInterface()) { | |
findMethods(selector, argTypes, currentType.methods(), scope, methodsFound, onlyStaticMethods, exactMatch); | |
ReferenceBinding[] itsInterfaces = currentType.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][]; | |
int lastPosition = 0; | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) { | |
currentType = interfaces[j]; | |
if ((currentType.tagBits & TagBits.InterfaceVisited) == 0) { // if interface as not already been visited | |
currentType.tagBits |= TagBits.InterfaceVisited; | |
findMethods(selector, argTypes, currentType.methods(), scope, methodsFound, onlyStaticMethods, exactMatch); | |
itsInterfaces = currentType.superInterfaces(); | |
if (itsInterfaces != NoSuperInterfaces) { | |
if (++lastPosition == interfacesToVisit.length) | |
System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); | |
interfacesToVisit[lastPosition] = itsInterfaces; | |
} | |
} | |
} | |
} | |
// bit reinitialization | |
for (int i = 0; i <= lastPosition; i++) { | |
ReferenceBinding[] interfaces = interfacesToVisit[i]; | |
for (int j = 0, length = interfaces.length; j < length; j++) | |
interfaces[j].tagBits &= ~TagBits.InterfaceVisited; | |
} | |
} | |
currentType = scope.getJavaLangObject(); | |
} | |
while (currentType != null) { | |
findMethods(selector, argTypes, currentType.methods(), scope, methodsFound, onlyStaticMethods, exactMatch); | |
currentType = currentType.superclass(); | |
} | |
} | |
private void findNestedTypes(char[] typeName, ReferenceBinding currentType, Scope scope) { | |
if (typeName == null) return; | |
int typeLength = typeName.length; | |
while (scope != null) { // done when a COMPILATION_UNIT_SCOPE is found | |
switch (scope.kind) { | |
case Scope.METHOD_SCOPE : | |
case Scope.BLOCK_SCOPE : | |
BlockScope blockScope = (BlockScope) scope; | |
next : for (int i = 0, length = blockScope.scopeIndex; i < length; i++) { | |
if (blockScope.subscopes[i] instanceof ClassScope) { | |
SourceTypeBinding localType = ((ClassScope) blockScope.subscopes[i]).referenceContext.binding; | |
if (!localType.isAnonymousType()) { | |
if (typeLength > localType.sourceName.length) continue next; | |
if (!CharOperation.prefixEquals(typeName, localType.sourceName, false /* ignore case */)) continue next; | |
requestor.acceptClass( | |
localType.qualifiedPackageName(), | |
localType.sourceName, | |
localType.sourceName, | |
localType.modifiers, | |
startPosition, | |
endPosition); | |
} | |
} | |
} | |
break; | |
case Scope.CLASS_SCOPE : | |
findMemberTypes(typeName, scope.enclosingSourceType(), scope); | |
if (typeLength == 0) return; // do not search outside the class scope if no prefix was provided | |
break; | |
case Scope.COMPILATION_UNIT_SCOPE : | |
return; | |
} | |
scope = scope.parent; | |
} | |
} | |
private void findPackages(CompletionOnPackageReference packageStatement) { | |
char[] packageName = CharOperation.concatWith(packageStatement.tokens, '.'); | |
if (packageName.length == 0) return; | |
setSourceRange(packageStatement.sourceStart, packageStatement.sourceEnd); | |
nameEnvironment.findPackages(CharOperation.toLowerCase(packageName), this); | |
} | |
private void findTypesAndPackages(char[] token, Scope scope) { | |
if (token == null) return; | |
if (scope.enclosingSourceType() != null) | |
findNestedTypes(token, scope.enclosingSourceType(), scope); | |
if (unitScope != null) { | |
int typeLength = token.length; | |
SourceTypeBinding[] types = unitScope.topLevelTypes; | |
for (int i = 0, length = types.length; i < length; i++) { | |
SourceTypeBinding sourceType = types[i]; | |
if (typeLength > sourceType.sourceName.length) continue; | |
if (!CharOperation.prefixEquals(token, sourceType.sourceName, false /* ignore case */)) continue; | |
requestor.acceptType( | |
sourceType.qualifiedPackageName(), | |
sourceType.sourceName(), | |
sourceType.sourceName(), | |
startPosition, | |
endPosition); | |
} | |
} | |
if (token.length == 0) return; | |
findKeywords(token, baseTypes, scope); | |
nameEnvironment.findTypes(token, this); | |
nameEnvironment.findPackages(token, this); | |
} | |
private void findTypesAndSubpackages(char[] token, PackageBinding packageBinding) { | |
char[] qualifiedName = CharOperation.concatWith(packageBinding.compoundName, token, '.'); | |
if (token == null || token.length == 0) { | |
int length = qualifiedName.length; | |
System.arraycopy(qualifiedName, 0, qualifiedName = new char[length + 1], 0, length); | |
qualifiedName[length] = '.'; | |
} | |
nameEnvironment.findTypes(qualifiedName, this); | |
nameEnvironment.findPackages(qualifiedName, this); | |
} | |
private void findVariablesAndMethods(char[] token, Scope scope) { | |
if (token == null) return; | |
// Should local variables hide fields from the receiver type or any of its enclosing types? | |
// we know its an implicit field/method access... see BlockScope getBinding/getImplicitMethod | |
boolean staticsOnly = false; // need to know if we're in a static context (or inside a constructor) | |
char[][] found = null; | |
int lastPosition = -1; | |
int tokenLength = token.length; | |
ObjectVector fieldsFound = new ObjectVector(); | |
ObjectVector methodsFound = new ObjectVector(); | |
done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found | |
switch (scope.kind) { | |
case Scope.METHOD_SCOPE : | |
// handle the error case inside an explicit constructor call (see MethodScope>>findField) | |
MethodScope methodScope = (MethodScope) scope; | |
staticsOnly |= methodScope.isStatic | methodScope.isConstructorCall; | |
case Scope.BLOCK_SCOPE : | |
BlockScope blockScope = (BlockScope) scope; | |
next : for (int i = 0, length = blockScope.locals.length; i < length; i++) { | |
LocalVariableBinding local = blockScope.locals[i]; | |
if (local == null) break next; | |
if (tokenLength > local.name.length) continue next; | |
if (!CharOperation.prefixEquals(token, local.name, false /* ignore case */)) continue next; | |
if (local.isSecret()) continue next; | |
if (found == null) { | |
found = new char[5][]; | |
} else { | |
for (int f = 0; f < found.length; f++) { | |
char[] name = found[f]; | |
if (name == null) break; | |
if (CharOperation.equals(name, local.name, false /* ignore case */)) continue next; | |
} | |
} | |
if (++lastPosition == found.length) | |
System.arraycopy(found, 0, found = new char[lastPosition * 2][], 0, lastPosition); | |
found[lastPosition] = local.name; | |
requestor.acceptLocalVariable( | |
local.name, | |
NoChar, | |
local.type == null ? local.declaration.type.toString().toCharArray() : local.type.qualifiedSourceName(), | |
local.modifiers, | |
startPosition, | |
endPosition); | |
} | |
break; | |
case Scope.CLASS_SCOPE : | |
ClassScope classScope = (ClassScope) scope; | |
SourceTypeBinding enclosingType = classScope.referenceContext.binding; | |
/* if (tokenLength == 0) { // only search inside the type itself if no prefix was provided | |
findFields(token, enclosingType.fields(), classScope, fieldsFound, staticsOnly); | |
findMethods(token, enclosingType.methods(), classScope, methodsFound, staticsOnly, false); | |
break done; | |
} else { */ | |
findFields(token, enclosingType, classScope, fieldsFound, staticsOnly); | |
findMethods(token, null, enclosingType, classScope, methodsFound, staticsOnly, false); | |
staticsOnly |= enclosingType.isStatic(); | |
// } | |
break; | |
case Scope.COMPILATION_UNIT_SCOPE : | |
break done; | |
} | |
scope = scope.parent; | |
} | |
} | |
public AssistParser getParser(){ | |
return parser; | |
} | |
private boolean mustQualifyType(char[][] packageName, char[] readableTypeName) { | |
// If there are no types defined into the current CU yet. | |
if (unitScope == null) | |
return true; | |
if (CharOperation.equals(unitScope.fPackage.compoundName, packageName)) | |
return false; | |
ImportBinding[] imports = unitScope.imports; | |
for (int i = 0, length = imports.length; i < length; i++) { | |
if (imports[i].onDemand) { | |
if (CharOperation.equals(imports[i].compoundName, packageName)) | |
return false; // how do you match p1.p2.A.* ? | |
} else if (CharOperation.equals(imports[i].readableName(), readableTypeName)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
protected void reset() { | |
super.reset(); | |
this.knownPkgs = new HashtableOfObject(10); | |
} | |
private void setSourceRange(int start, int end) { | |
this.startPosition = start; | |
this.endPosition = end + 1; // Add 1 for now | |
} | |
} |