/*******************************************************************************
* Copyright (c) 2005, 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.util;
import java.util.ArrayList;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.CaptureBinding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
public class BindingKeyResolver extends BindingKeyParser {
Compiler compiler;
Binding compilerBinding;
char[][] compoundName;
int dimension;
LookupEnvironment environment;
ReferenceBinding genericType;
MethodBinding methodBinding;
AnnotationBinding annotationBinding;
char[] secondarySimpleName;
CompilationUnitDeclaration parsedUnit;
BlockScope scope;
TypeBinding typeBinding;
TypeDeclaration typeDeclaration;
ArrayList types= new ArrayList();
int wildcardRank;
CompilationUnitDeclaration outerMostParsedUnit;
/*
* A hash set of the file names of already resolved units
*/
HashtableOfObject resolvedUnits;
private BindingKeyResolver(BindingKeyParser parser, Compiler compiler, LookupEnvironment environment, CompilationUnitDeclaration outerMostParsedUnit, HashtableOfObject parsedUnits) {
super(parser);
this.compiler= compiler;
this.environment= environment;
this.outerMostParsedUnit= outerMostParsedUnit;
this.resolvedUnits= parsedUnits;
}
public BindingKeyResolver(String key, Compiler compiler, LookupEnvironment environment) {
super(key);
this.compiler= compiler;
this.environment= environment;
this.resolvedUnits= new HashtableOfObject();
}
/*
* If not already cached, computes and cache the compound name (pkg name + top level name) of this key.
* Returns the package name if key is a pkg key.
* Returns an empty array if malformed.
* This key's scanner should be positioned on the package or type token.
*/
public char[][] compoundName() {
return this.compoundName;
}
public void consumeAnnotation() {
int size= this.types.size();
if (size == 0)
return;
Binding annotationType= ((BindingKeyResolver)this.types.get(size - 1)).compilerBinding;
AnnotationBinding[] annotationBindings;
if (this.compilerBinding == null && this.typeBinding instanceof ReferenceBinding) {
annotationBindings= ((ReferenceBinding)this.typeBinding).getAnnotations();
} else if (this.compilerBinding instanceof MethodBinding) {
annotationBindings= ((MethodBinding)this.compilerBinding).getAnnotations();
} else if (this.compilerBinding instanceof VariableBinding) {
annotationBindings= ((VariableBinding)this.compilerBinding).getAnnotations();
} else {
return;
}
for (int i= 0, length= annotationBindings.length; i < length; i++) {
AnnotationBinding binding= annotationBindings[i];
if (binding.getAnnotationType() == annotationType) {
this.annotationBinding= binding;
break;
}
}
}
public void consumeArrayDimension(char[] brakets) {
this.dimension= brakets.length;
}
public void consumeBaseType(char[] baseTypeSig) {
this.compoundName= new char[][] { getKey().toCharArray() };
TypeBinding baseTypeBinding= getBaseTypeBinding(baseTypeSig);
if (baseTypeBinding != null) {
this.typeBinding= baseTypeBinding;
}
}
public void consumeCapture(final int position) {
CompilationUnitDeclaration outerParsedUnit= this.outerMostParsedUnit == null ? this.parsedUnit : this.outerMostParsedUnit;
if (outerParsedUnit == null)
return;
final Binding wildcardBinding= ((BindingKeyResolver)this.types.get(0)).compilerBinding;
class CaptureFinder extends ASTVisitor {
CaptureBinding capture;
boolean checkType(TypeBinding binding) {
if (binding == null)
return false;
switch (binding.kind()) {
case Binding.PARAMETERIZED_TYPE:
TypeBinding[] arguments= ((ParameterizedTypeBinding)binding).arguments;
if (arguments == null)
return false;
for (int i= 0, length= arguments.length; i < length; i++) {
if (checkType(arguments[i]))
return true;
}
break;
case Binding.WILDCARD_TYPE:
return checkType(((WildcardBinding)binding).bound);
case Binding.INTERSECTION_TYPE:
if (checkType(((WildcardBinding)binding).bound))
return true;
TypeBinding[] otherBounds= ((WildcardBinding)binding).otherBounds;
// per construction, otherBounds is never null
for (int i= 0, length= otherBounds.length; i < length; i++) {
if (checkType(otherBounds[i]))
return true;
}
break;
case Binding.ARRAY_TYPE:
return checkType(((ArrayBinding)binding).leafComponentType);
case Binding.TYPE_PARAMETER:
if (binding.isCapture()) {
CaptureBinding captureBinding= (CaptureBinding)binding;
if (captureBinding.position == position && captureBinding.wildcard == wildcardBinding) {
this.capture= captureBinding;
return true;
}
}
break;
}
return false;
}
public boolean visit(SingleNameReference singleNameReference, BlockScope blockScope) {
if (checkType(singleNameReference.resolvedType))
return false;
return super.visit(singleNameReference, blockScope);
}
public boolean visit(QualifiedNameReference qualifiedNameReference, BlockScope blockScope) {
if (checkType(qualifiedNameReference.resolvedType))
return false;
return super.visit(qualifiedNameReference, blockScope);
}
public boolean visit(MessageSend messageSend, BlockScope blockScope) {
if (checkType(messageSend.resolvedType))
return false;
return super.visit(messageSend, blockScope);
}
public boolean visit(FieldReference fieldReference, BlockScope blockScope) {
if (checkType(fieldReference.resolvedType))
return false;
return super.visit(fieldReference, blockScope);
}
public boolean visit(ConditionalExpression conditionalExpression, BlockScope blockScope) {
if (checkType(conditionalExpression.resolvedType))
return false;
return super.visit(conditionalExpression, blockScope);
}
public boolean visit(CastExpression castExpression, BlockScope blockScope) {
if (checkType(castExpression.resolvedType))
return false;
return super.visit(castExpression, blockScope);
}
public boolean visit(Assignment assignment, BlockScope blockScope) {
if (checkType(assignment.resolvedType))
return false;
return super.visit(assignment, blockScope);
}
public boolean visit(ArrayReference arrayReference, BlockScope blockScope) {
if (checkType(arrayReference.resolvedType))
return false;
return super.visit(arrayReference, blockScope);
}
}
CaptureFinder captureFinder= new CaptureFinder();
outerParsedUnit.traverse(captureFinder, outerParsedUnit.scope);
this.typeBinding= captureFinder.capture;
}
public void consumeException() {
this.types= new ArrayList();
}
public void consumeField(char[] fieldName) {
if (this.typeBinding == null)
return;
FieldBinding[] fields= ((ReferenceBinding)this.typeBinding).availableFields(); // resilience
for (int i= 0, length= fields.length; i < length; i++) {
FieldBinding field= fields[i];
if (CharOperation.equals(fieldName, field.name)) {
this.typeBinding= null;
this.compilerBinding= field;
return;
}
}
}
public void consumeParameterizedGenericMethod() {
if (this.methodBinding == null)
return;
TypeBinding[] arguments= getTypeBindingArguments();
if (arguments == null) {
this.methodBinding= null;
this.compilerBinding= null;
return;
}
if (arguments.length != this.methodBinding.typeVariables().length)
this.methodBinding= this.environment.createParameterizedGenericMethod(this.methodBinding, (RawTypeBinding)null);
else
this.methodBinding= this.environment.createParameterizedGenericMethod(this.methodBinding, arguments);
this.compilerBinding= this.methodBinding;
}
public void consumeLocalType(char[] uniqueKey) {
LocalTypeBinding[] localTypeBindings= this.parsedUnit.localTypes;
for (int i= 0; i < this.parsedUnit.localTypeCount; i++)
if (CharOperation.equals(uniqueKey, localTypeBindings[i].computeUniqueKey(false/*not a leaf*/))) {
this.typeBinding= localTypeBindings[i];
return;
}
}
public void consumeLocalVar(char[] varName, int occurrenceCount) {
if (this.scope == null) {
if (this.methodBinding == null)
return;
this.scope= this.methodBinding.sourceMethod().scope;
}
for (int i= 0; i < this.scope.localIndex; i++) {
LocalVariableBinding local= this.scope.locals[i];
if (CharOperation.equals(local.name, varName)
&& occurrenceCount-- == 0) {
this.methodBinding= null;
this.compilerBinding= local;
return;
}
}
}
public void consumeMethod(char[] selector, char[] signature) {
if (this.typeBinding == null)
return;
MethodBinding[] methods= ((ReferenceBinding)this.typeBinding).availableMethods(); // resilience
for (int i= 0, methodLength= methods.length; i < methodLength; i++) {
MethodBinding method= methods[i];
if (CharOperation.equals(selector, method.selector) || (selector.length == 0 && method.isConstructor())) {
char[] methodSignature= method.genericSignature();
if (methodSignature == null)
methodSignature= method.signature();
if (CharOperation.equals(signature, methodSignature)) {
this.typeBinding= null;
this.methodBinding= method;
this.compilerBinding= this.methodBinding;
return;
}
}
}
}
public void consumeMemberType(char[] simpleTypeName) {
this.typeBinding= getTypeBinding(simpleTypeName);
}
public void consumePackage(char[] pkgName) {
this.compoundName= CharOperation.splitOn('/', pkgName);
this.compilerBinding= new PackageBinding(this.compoundName, null, this.environment);
}
public void consumeParameterizedType(char[] simpleTypeName, boolean isRaw) {
if (this.typeBinding == null)
return;
TypeBinding[] arguments= getTypeBindingArguments();
if (arguments == null) {
this.typeBinding= null;
this.genericType= null;
return;
}
if (simpleTypeName != null) {
if (this.genericType == null) {
// parameterized member type with raw enclosing type
this.genericType= ((ReferenceBinding)this.typeBinding).getMemberType(simpleTypeName);
} else {
// parameterized member type with parameterized enclosing type
this.genericType= this.genericType.getMemberType(simpleTypeName);
}
if (!isRaw)
this.typeBinding= this.environment.createParameterizedType(this.genericType, arguments, (ReferenceBinding)this.typeBinding);
else
// raw type
this.typeBinding= this.environment.createRawType(this.genericType, (ReferenceBinding)this.typeBinding);
} else {
// parameterized top level type or parameterized member type with raw enclosing type
this.genericType= (ReferenceBinding)this.typeBinding;
ReferenceBinding enclosing= this.genericType.enclosingType();
if (enclosing != null)
enclosing= (ReferenceBinding)this.environment.convertToRawType(enclosing, false /*do not force conversion of enclosing types*/);
this.typeBinding= this.environment.createParameterizedType(this.genericType, arguments, enclosing);
}
}
public void consumeParser(BindingKeyParser parser) {
this.types.add(parser);
}
public void consumeScope(int scopeNumber) {
if (this.scope == null) {
if (this.methodBinding == null)
return;
this.scope= this.methodBinding.sourceMethod().scope;
}
if (scopeNumber >= this.scope.subscopeCount)
return; // malformed key
this.scope= (BlockScope)this.scope.subscopes[scopeNumber];
}
public void consumeRawType() {
if (this.typeBinding == null)
return;
this.typeBinding= this.environment.convertToRawType(this.typeBinding, false /*do not force conversion of enclosing types*/);
}
public void consumeSecondaryType(char[] simpleTypeName) {
this.secondarySimpleName= simpleTypeName;
}
public void consumeFullyQualifiedName(char[] fullyQualifiedName) {
this.compoundName= CharOperation.splitOn('/', fullyQualifiedName);
}
public void consumeTopLevelType() {
char[] fileName;
this.parsedUnit= getCompilationUnitDeclaration();
if (this.parsedUnit != null && this.compiler != null && !this.resolvedUnits.containsKey(fileName= this.parsedUnit.getFileName())) {
this.compiler.process(this.parsedUnit, this.compiler.totalUnits + 1); // unit is resolved only once thanks to the resolvedUnits protection
this.resolvedUnits.put(fileName, fileName);
}
if (this.parsedUnit == null) {
this.typeBinding= getBinaryBinding();
} else {
char[] typeName= this.secondarySimpleName == null ? this.compoundName[this.compoundName.length - 1] : this.secondarySimpleName;
this.typeBinding= getTypeBinding(typeName);
}
}
public void consumeKey() {
if (this.typeBinding != null) {
this.typeBinding= getArrayBinding(this.dimension, this.typeBinding);
this.compilerBinding= this.typeBinding;
}
}
public void consumeTypeVariable(char[] position, char[] typeVariableName) {
if (position.length > 0) {
if (this.typeBinding == null)
return;
int pos= Integer.parseInt(new String(position));
MethodBinding[] methods= ((ReferenceBinding)this.typeBinding).availableMethods(); // resilience
if (methods != null && pos < methods.length) {
this.methodBinding= methods[pos];
}
}
TypeVariableBinding[] typeVariableBindings;
if (this.methodBinding != null) {
typeVariableBindings= this.methodBinding.typeVariables();
} else if (this.typeBinding != null) {
typeVariableBindings= this.typeBinding.typeVariables();
} else {
return;
}
for (int i= 0, length= typeVariableBindings.length; i < length; i++) {
TypeVariableBinding typeVariableBinding= typeVariableBindings[i];
if (CharOperation.equals(typeVariableName, typeVariableBinding.sourceName())) {
this.typeBinding= typeVariableBinding;
return;
}
}
}
public void consumeTypeWithCapture() {
BindingKeyResolver resolver= (BindingKeyResolver)this.types.get(0);
this.typeBinding= (TypeBinding)resolver.compilerBinding;
}
public void consumeWildcardRank(int aRank) {
this.wildcardRank= aRank;
}
public void consumeWildCard(int kind) {
switch (kind) {
case Wildcard.EXTENDS:
case Wildcard.SUPER:
BindingKeyResolver boundResolver= (BindingKeyResolver)this.types.get(0);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=157847, do not allow creation of
// internally inconsistent wildcards of the form '? super <null>' or '? extends <null>'
final Binding boundBinding= boundResolver.compilerBinding;
if (boundBinding instanceof TypeBinding) {
this.typeBinding= this.environment.createWildcard((ReferenceBinding)this.typeBinding, this.wildcardRank, (TypeBinding)boundBinding, null /*no extra bound*/, kind);
} else {
this.typeBinding= null;
}
break;
case Wildcard.UNBOUND:
this.typeBinding= this.environment.createWildcard((ReferenceBinding)this.typeBinding, this.wildcardRank, null/*no bound*/, null /*no extra bound*/, kind);
break;
}
}
public AnnotationBinding getAnnotationBinding() {
return this.annotationBinding;
}
/*
* If the given dimension is greater than 0 returns an array binding for the given type binding.
* Otherwise return the given type binding.
* Returns null if the given type binding is null.
*/
private TypeBinding getArrayBinding(int dim, TypeBinding binding) {
if (binding == null)
return null;
if (dim == 0)
return binding;
return this.environment.createArrayType(binding, dim);
}
private TypeBinding getBaseTypeBinding(char[] signature) {
switch (signature[0]) {
case 'I':
return TypeBinding.INT;
case 'Z':
return TypeBinding.BOOLEAN;
case 'V':
return TypeBinding.VOID;
case 'C':
return TypeBinding.CHAR;
case 'D':
return TypeBinding.DOUBLE;
case 'B':
return TypeBinding.BYTE;
case 'F':
return TypeBinding.FLOAT;
case 'J':
return TypeBinding.LONG;
case 'S':
return TypeBinding.SHORT;
case 'N':
return TypeBinding.NULL;
default:
return null;
}
}
/*
* Returns a binary binding corresonding to this key's compound name.
* Returns null if not found.
*/
private TypeBinding getBinaryBinding() {
if (this.compoundName.length == 0)
return null;
return this.environment.getType(this.compoundName);
}
/*
* Finds the compilation unit declaration corresponding to the key in the given lookup environment.
* Returns null if no compilation unit declaration could be found.
* This key's scanner should be positioned on the package token.
*/
public CompilationUnitDeclaration getCompilationUnitDeclaration() {
char[][] name= this.compoundName;
if (name.length == 0)
return null;
if (this.environment == null)
return null;
ReferenceBinding binding= this.environment.getType(name);
if (!(binding instanceof SourceTypeBinding)) {
if (this.secondarySimpleName == null)
return null;
// case of a secondary type with no primary type (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=177115)
int length= name.length;
System.arraycopy(name, 0, name= new char[length][], 0, length - 1);
name[length - 1]= this.secondarySimpleName;
binding= this.environment.getType(name);
if (!(binding instanceof SourceTypeBinding))
return null;
}
SourceTypeBinding sourceTypeBinding= (SourceTypeBinding)binding;
if (sourceTypeBinding.scope == null)
return null;
return sourceTypeBinding.scope.compilationUnitScope().referenceContext;
}
/*
* Returns the compiler binding corresponding to this key.
* Returns null is malformed.
* This key's scanner should be positioned on the package token.
*/
public Binding getCompilerBinding() {
try {
parse();
return this.compilerBinding;
} catch (RuntimeException e) {
Util.log(e, "Could not create binding from binding key: " + getKey()); //$NON-NLS-1$
return null;
}
}
private TypeBinding getTypeBinding(char[] simpleTypeName) {
if (this.typeBinding instanceof ReferenceBinding) {
return ((ReferenceBinding)this.typeBinding).getMemberType(simpleTypeName);
}
TypeDeclaration[] typeDeclarations=
this.typeDeclaration == null ?
(this.parsedUnit == null ? null : this.parsedUnit.types) :
this.typeDeclaration.memberTypes;
if (typeDeclarations == null)
return null;
for (int i= 0, length= typeDeclarations.length; i < length; i++) {
TypeDeclaration declaration= typeDeclarations[i];
if (CharOperation.equals(simpleTypeName, declaration.name)) {
this.typeDeclaration= declaration;
return declaration.binding;
}
}
return null;
}
private TypeBinding[] getTypeBindingArguments() {
int size= this.types.size();
TypeBinding[] arguments= new TypeBinding[size];
for (int i= 0; i < size; i++) {
BindingKeyResolver resolver= (BindingKeyResolver)this.types.get(i);
TypeBinding compilerBinding2= (TypeBinding)resolver.compilerBinding;
if (compilerBinding2 == null) {
this.types= new ArrayList();
return null;
}
arguments[i]= compilerBinding2;
}
this.types= new ArrayList();
return arguments;
}
public void malformedKey() {
this.compoundName= CharOperation.NO_CHAR_CHAR;
}
public BindingKeyParser newParser() {
return new BindingKeyResolver(this, this.compiler, this.environment, this.outerMostParsedUnit == null ? this.parsedUnit : this.outerMostParsedUnit, this.resolvedUnits);
}
public String toString() {
return getKey();
}
}