/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.eclipse.core.model.mirror;
import static com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.toType;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities;
import com.redhat.ceylon.eclipse.core.model.ModelLoaderNameEnvironment;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.ide.common.model.UnknownTypeMirror;
import com.redhat.ceylon.model.loader.AbstractModelLoader;
import com.redhat.ceylon.model.loader.mirror.ClassMirror;
import com.redhat.ceylon.model.loader.mirror.FunctionalInterfaceType;
import com.redhat.ceylon.model.loader.mirror.TypeKind;
import com.redhat.ceylon.model.loader.mirror.TypeMirror;
import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror;
public class JDTType implements TypeMirror {
public static final TypeMirror UNKNOWN_TYPE = new UnknownTypeMirror();
private String qualifiedName;
private List<TypeMirror> typeArguments;
private TypeKind typeKind;
private TypeMirror componentType;
private TypeMirror upperBound;
private TypeMirror lowerBound;
private ClassMirror declaredClass;
private JDTTypeParameter typeParameter;
private boolean isPrimitive;
private boolean isRaw;
private TypeMirror qualifyingType;
private FunctionalInterfaceType functionalInterfaceType = null;
public FunctionalInterfaceType getFunctionalInterfaceType() {
return functionalInterfaceType;
}
public static TypeMirror newJDTType(TypeBinding type) {
return newJDTType(type, new IdentityHashMap<TypeBinding, JDTType>());
}
private static TypeMirror unknownTypeMirror(TypeBinding type) {
try {
if (type.qualifiedSourceName() != null) {
return new UnknownTypeMirror(JDTUtils.getFullyQualifiedName(type));
}
} catch(Exception e) {
e.printStackTrace();
}
return UNKNOWN_TYPE;
}
static private LookupEnvironmentUtilities.Provider getModelLoader(TypeBinding binding) {
PackageBinding pkgBinding = binding.getPackage();
if (pkgBinding == null) {
return null;
}
LookupEnvironment lookupEnv = pkgBinding.environment;
INameEnvironment nameEnv = lookupEnv.nameEnvironment;
if (! (nameEnv instanceof ModelLoaderNameEnvironment)) {
return null;
}
IJavaProject javaProject = ((ModelLoaderNameEnvironment)nameEnv).getJavaProject();
AbstractModelLoader modelLoader = modelJ2C().javaProjectModelLoader(javaProject);
if (modelLoader instanceof LookupEnvironmentUtilities.Provider) {
return (LookupEnvironmentUtilities.Provider) modelLoader;
}
return null;
}
static TypeMirror newJDTType(TypeBinding type, IdentityHashMap<TypeBinding, JDTType> originatingTypes) {
if (type instanceof UnresolvedReferenceBinding) {
type = BinaryTypeBinding.resolveType(type, type.getPackage().environment, false);
}
if (type instanceof MissingTypeBinding) {
return unknownTypeMirror(type);
}
if (type instanceof ParameterizedTypeBinding) {
TypeBinding genericType = ((ParameterizedTypeBinding)type).genericType();
if (genericType instanceof MissingTypeBinding) {
return unknownTypeMirror(genericType);
}
}
if (type instanceof ProblemReferenceBinding) {
ProblemReferenceBinding prb = (ProblemReferenceBinding) type;
TypeBinding closestMatch = prb.closestMatch();
if (closestMatch != null && closestMatch != type) {
return newJDTType(closestMatch, originatingTypes);
}
return unknownTypeMirror(type);
}
TypeMirror typeMirror = null;
char[] bindingKey = null;
LookupEnvironmentUtilities.Provider modelLoader = getModelLoader(type);
if (modelLoader != null) {
bindingKey = type.computeUniqueKey();
typeMirror = modelLoader.getCachedTypeMirror(bindingKey);
}
if (typeMirror == null) {
typeMirror = createJDTType(type, originatingTypes);
if (modelLoader != null) {
modelLoader.cacheTypeMirror(bindingKey, typeMirror);
}
}
return typeMirror;
}
private static TypeMirror createJDTType(TypeBinding type,
IdentityHashMap<TypeBinding, JDTType> originatingTypes) {
JDTType typeMirror = new JDTType();
originatingTypes.put(type, typeMirror);
if (type instanceof UnresolvedReferenceBinding) {
type = BinaryTypeBinding.resolveType(type, type.getPackage().environment, false);
}
// type params are not qualified
if(type instanceof TypeVariableBinding)
typeMirror.qualifiedName = new String(type.qualifiedSourceName());
else
typeMirror.qualifiedName = JDTUtils.getFullyQualifiedName(type);
typeMirror.typeKind = findKind(type);
typeMirror.isPrimitive = type.isBaseType() &&
type.id != TypeIds.T_void &&
type.id != TypeIds.T_null;
typeMirror.isRaw = type.isRawType();
if(type instanceof ParameterizedTypeBinding && ! (type instanceof RawTypeBinding)){
TypeBinding[] javaTypeArguments = ((ParameterizedTypeBinding)type).typeArguments();
if (javaTypeArguments == null) {
javaTypeArguments = new TypeBinding[0];
}
typeMirror.typeArguments = new ArrayList<TypeMirror>(javaTypeArguments.length);
for(TypeBinding typeArgument : javaTypeArguments)
typeMirror.typeArguments.add(toTypeMirror(typeArgument, type, typeMirror, originatingTypes));
}
else {
typeMirror.typeArguments = Collections.emptyList();
}
if(type.enclosingType() instanceof ParameterizedTypeBinding){
boolean isStatic = false;
if (type instanceof ReferenceBinding) {
ReferenceBinding referenceBinding = (ReferenceBinding) type;
if ((referenceBinding.modifiers & ClassFileConstants.AccStatic) != 0) {
isStatic = true;
}
}
if (!isStatic) {
typeMirror.qualifyingType = toTypeMirror(type.enclosingType(), type, typeMirror, originatingTypes);
}
}
if (type instanceof ArrayBinding) {
TypeBinding jdtComponentType = ((ArrayBinding)type).elementsType();
typeMirror.componentType = toTypeMirror(jdtComponentType, type, typeMirror, originatingTypes);
} else {
typeMirror.componentType = null;
}
if (type.isWildcard()) {
WildcardBinding wildcardBinding = (WildcardBinding) type;
if (wildcardBinding.boundKind == Wildcard.EXTENDS) {
TypeBinding upperBoundBinding = wildcardBinding.bound;
if (upperBoundBinding != null) {
typeMirror.upperBound = toTypeMirror(upperBoundBinding, type, typeMirror, originatingTypes);
}
}
} else if (type.isTypeVariable()){
TypeVariableBinding typeVariableBinding = (TypeVariableBinding) type;
TypeBinding boundBinding = typeVariableBinding.firstBound; // TODO : we should confirm this
if (boundBinding != null) {
typeMirror.upperBound = toTypeMirror(boundBinding, type, typeMirror, originatingTypes);
}
} else {
typeMirror.upperBound = null;
}
if (type.isWildcard()) {
WildcardBinding wildcardBinding = (WildcardBinding) type;
if (wildcardBinding.boundKind == Wildcard.SUPER) {
TypeBinding lowerBoundBinding = wildcardBinding.bound;
if (lowerBoundBinding != null) {
typeMirror.lowerBound = toTypeMirror(lowerBoundBinding, type, typeMirror, originatingTypes);
}
}
}
if(type instanceof ParameterizedTypeBinding) {
ParameterizedTypeBinding refBinding = (ParameterizedTypeBinding) type;
TypeBinding genericType = refBinding.genericType();
if (genericType instanceof MissingTypeBinding) {
return unknownTypeMirror(genericType);
} else {
typeMirror.declaredClass = new JDTClass(refBinding, toType(refBinding.genericType()));
}
} else if(type instanceof SourceTypeBinding ||
type instanceof BinaryTypeBinding){
ReferenceBinding refBinding = (ReferenceBinding) type;
typeMirror.declaredClass = new JDTClass(refBinding, toType(refBinding));
}
if(type instanceof TypeVariableBinding){
typeMirror.typeParameter = new JDTTypeParameter((TypeVariableBinding) type, typeMirror, originatingTypes);
}
if (type instanceof ReferenceBinding) {
ReferenceBinding referenceBinding = (ReferenceBinding) type;
PackageBinding p = referenceBinding.getPackage();
if (p != null) {
LookupEnvironment environment = p.environment;
if (referenceBinding.isInterface()) {
try {
Scope scope = new CompilationUnitScope(
new CompilationUnitDeclaration(
environment.problemReporter,
null,
0), environment);
MethodBinding method = referenceBinding.getSingleAbstractMethod(scope, true);
if (method != null &&
method.isValidBinding()) {
ReferenceBinding enclosingClass = method.declaringClass;
TypeBinding[] parameters = method.parameters;
ArrayList<TypeMirror> jdtTypes = new ArrayList<TypeMirror>();
if (parameters.length > 0) {
for (int i=0; i<parameters.length -1; i++) {
jdtTypes.add(toTypeMirror(parameters[i], type, typeMirror, originatingTypes));
}
TypeBinding lastParameterBinding = parameters[parameters.length-1];
if (method.isVarargs() &&
lastParameterBinding instanceof ArrayBinding) {
jdtTypes.add(toTypeMirror(((ArrayBinding)lastParameterBinding).elementsType(), type, typeMirror, originatingTypes));
} else {
jdtTypes.add(toTypeMirror(lastParameterBinding, type, typeMirror, originatingTypes));
}
}
typeMirror.functionalInterfaceType = new FunctionalInterfaceType(
new JDTMethod(new JDTClass(enclosingClass, LookupEnvironmentUtilities.toType(enclosingClass)), method),
toTypeMirror(method.returnType, type, typeMirror, originatingTypes),
jdtTypes,
method.isVarargs());
}
} catch(Exception e) {
CeylonPlugin.log(Status.ERROR, "Exception when trying to retrieve Functional interface of type" + referenceBinding.debugName() +
"\n -> functional interface search skipped:", e);
}
}
}
}
return typeMirror;
}
public static TypeMirror toTypeMirror(TypeBinding typeBinding, TypeBinding currentBinding, JDTType currentType, IdentityHashMap<TypeBinding, JDTType> originatingTypes) {
if (typeBinding == currentBinding && currentType != null) {
return currentType;
}
JDTType originatingType = originatingTypes.get(typeBinding);
if (originatingType != null) {
return originatingType;
}
return newJDTType(typeBinding, originatingTypes);
}
@Override
public String getQualifiedName() {
return qualifiedName;
}
@Override
public List<TypeMirror> getTypeArguments() {
return typeArguments;
}
@Override
public TypeKind getKind() {
return typeKind;
}
private static TypeKind findKind(TypeBinding type) {
if(type instanceof ArrayBinding)
return TypeKind.ARRAY;
if(type instanceof TypeVariableBinding)
return TypeKind.TYPEVAR;
if(type instanceof WildcardBinding)
return TypeKind.WILDCARD;
if(type instanceof BaseTypeBinding){
switch(type.id) {
case TypeIds.T_boolean : return TypeKind.BOOLEAN;
case TypeIds.T_byte : return TypeKind.BYTE;
case TypeIds.T_char : return TypeKind.CHAR;
case TypeIds.T_short : return TypeKind.SHORT;
case TypeIds.T_int : return TypeKind.INT;
case TypeIds.T_long : return TypeKind.LONG;
case TypeIds.T_float : return TypeKind.FLOAT;
case TypeIds.T_double : return TypeKind.DOUBLE;
case TypeIds.T_void : return TypeKind.VOID;
case TypeIds.T_null : return TypeKind.NULL;
}
}
if(type instanceof ReferenceBinding)
return TypeKind.DECLARED;
throw new RuntimeException("Unknown type: "+type);
}
@Override
public TypeMirror getComponentType() {
return componentType;
}
@Override
public boolean isPrimitive() {
return isPrimitive;
}
@Override
public TypeMirror getUpperBound() {
return upperBound;
}
@Override
public TypeMirror getLowerBound() {
return lowerBound;
}
@Override
public boolean isRaw() {
return isRaw;
}
@Override
public ClassMirror getDeclaredClass() {
return declaredClass;
}
@Override
public TypeParameterMirror getTypeParameter() {
return typeParameter;
}
@Override
public TypeMirror getQualifyingType() {
return qualifyingType;
}
@Override
public String toString() {
return "[JDTType: "+qualifiedName+"]";
}
}