| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.refactoring.typeMigration; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiImplUtil; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.HashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.Map; |
| |
| /** |
| * @author db |
| * Date: 27.06.2003 |
| */ |
| public class TypeEvaluator { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.typeMigration.TypeEvaluator"); |
| |
| private final HashMap<TypeMigrationUsageInfo, LinkedList<PsiType>> myTypeMap; |
| private final TypeMigrationRules myRules; |
| private final TypeMigrationLabeler myLabeler; |
| |
| |
| public TypeEvaluator(final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> types, final TypeMigrationLabeler labeler) { |
| myLabeler = labeler; |
| myRules = labeler.getRules(); |
| myTypeMap = new HashMap<TypeMigrationUsageInfo, LinkedList<PsiType>>(); |
| |
| for (final Pair<TypeMigrationUsageInfo, PsiType> p : types) { |
| final LinkedList<PsiType> e = new LinkedList<PsiType>(); |
| |
| e.addFirst(p.getSecond()); |
| |
| myTypeMap.put(p.getFirst(), e); |
| } |
| |
| } |
| |
| public boolean setType(final TypeMigrationUsageInfo usageInfo, @NotNull PsiType type) { |
| final LinkedList<PsiType> t = myTypeMap.get(usageInfo); |
| |
| final PsiElement element = usageInfo.getElement(); |
| |
| if (type instanceof PsiEllipsisType && !(element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiMethod)) { |
| type = ((PsiEllipsisType)type).toArrayType(); |
| } |
| |
| if (t != null) { |
| if (!t.getFirst().equals(type)) { |
| if (element instanceof PsiVariable || element instanceof PsiMethod) { |
| return false; |
| } |
| |
| t.addFirst(type); |
| |
| return true; |
| } |
| } |
| else { |
| final LinkedList<PsiType> e = new LinkedList<PsiType>(); |
| |
| e.addFirst(type); |
| |
| myTypeMap.put(usageInfo, e); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Nullable |
| public PsiType getType(PsiElement element) { |
| for (Map.Entry<TypeMigrationUsageInfo, LinkedList<PsiType>> entry : myTypeMap.entrySet()) { |
| if (Comparing.equal(element, entry.getKey().getElement())) { |
| return entry.getValue().getFirst(); |
| } |
| } |
| if (element.getTextRange() == null) return null; |
| return getType(new TypeMigrationUsageInfo(element)); |
| } |
| |
| @Nullable |
| public PsiType getType(final TypeMigrationUsageInfo usageInfo) { |
| final LinkedList<PsiType> e = myTypeMap.get(usageInfo); |
| |
| if (e != null) { |
| return e.getFirst(); |
| } |
| |
| return TypeMigrationLabeler.getElementType(usageInfo.getElement()); |
| } |
| |
| @Nullable |
| public PsiType evaluateType(final PsiExpression expr) { |
| if (expr == null) return null; |
| final LinkedList<PsiType> e = myTypeMap.get(new TypeMigrationUsageInfo(expr)); |
| |
| if (e != null) { |
| return e.getFirst(); |
| } |
| |
| if (expr instanceof PsiArrayAccessExpression) { |
| final PsiType at = evaluateType(((PsiArrayAccessExpression)expr).getArrayExpression()); |
| |
| if (at instanceof PsiArrayType) { |
| return ((PsiArrayType)at).getComponentType(); |
| } |
| } |
| else if (expr instanceof PsiAssignmentExpression) { |
| return evaluateType(((PsiAssignmentExpression)expr).getLExpression()); |
| } |
| else if (expr instanceof PsiMethodCallExpression) { |
| final PsiMethodCallExpression call = (PsiMethodCallExpression)expr; |
| final JavaResolveResult resolveResult = call.resolveMethodGenerics(); |
| final PsiMethod method = (PsiMethod)resolveResult.getElement(); |
| |
| if (method != null) { |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| final PsiExpression[] actualParms = call.getArgumentList().getExpressions(); |
| return PsiImplUtil.normalizeWildcardTypeByPosition(createMethodSubstitution(parameters, actualParms, method, call, resolveResult.getSubstitutor(), false).substitute(evaluateType(call.getMethodExpression())), expr); |
| } |
| } |
| else if (expr instanceof PsiPolyadicExpression) { |
| final PsiExpression[] operands = ((PsiPolyadicExpression)expr).getOperands(); |
| final IElementType sign = ((PsiPolyadicExpression)expr).getOperationTokenType(); |
| PsiType lType = operands.length > 0 ? evaluateType(operands[0]) : null; |
| for (int i = 1; i < operands.length; i++) { |
| lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, evaluateType(operands[i]), sign, true); |
| } |
| return lType; |
| } |
| else if (expr instanceof PsiPostfixExpression) { |
| return evaluateType(((PsiPostfixExpression)expr).getOperand()); |
| } |
| else if (expr instanceof PsiPrefixExpression) { |
| return evaluateType(((PsiPrefixExpression)expr).getOperand()); |
| } |
| else if (expr instanceof PsiParenthesizedExpression) { |
| return evaluateType(((PsiParenthesizedExpression)expr).getExpression()); |
| } |
| else if (expr instanceof PsiConditionalExpression) { |
| final PsiExpression thenExpression = ((PsiConditionalExpression)expr).getThenExpression(); |
| final PsiExpression elseExpression = ((PsiConditionalExpression)expr).getElseExpression(); |
| |
| final PsiType thenType = evaluateType(thenExpression); |
| final PsiType elseType = evaluateType(elseExpression); |
| |
| switch ((thenType == null ? 0 : 1) + (elseType == null ? 0 : 2)) { |
| case 0: |
| return expr.getType(); |
| |
| case 1: |
| return thenType; |
| |
| case 2: |
| return elseType; |
| |
| case 3: |
| if (TypeConversionUtil.areTypesConvertible(thenType, elseType)) { |
| return thenType; |
| } |
| else if (TypeConversionUtil.areTypesConvertible(elseType, thenType)) { |
| return elseType; |
| } else { |
| switch ((thenType.equals(thenExpression.getType()) ? 0 : 1) + (elseType.equals(elseExpression.getType()) ? 0 : 2)) { |
| case 0: |
| return expr.getType(); |
| |
| case 1: |
| return thenType; |
| |
| case 2: |
| return elseType; |
| |
| case 3: |
| return expr.getType(); |
| |
| default: |
| LOG.error("Must not happen."); |
| return null; |
| } |
| } |
| |
| default: |
| LOG.error("Must not happen."); |
| } |
| |
| } |
| else if (expr instanceof PsiNewExpression) { |
| final PsiExpression qualifier = ((PsiNewExpression)expr).getQualifier(); |
| |
| if (qualifier != null) { |
| final PsiClassType.ClassResolveResult qualifierResult = resolveType(evaluateType(qualifier)); |
| |
| if (qualifierResult.getElement() != null) { |
| final PsiSubstitutor qualifierSubs = qualifierResult.getSubstitutor(); |
| final PsiClassType.ClassResolveResult result = resolveType(expr.getType()); |
| |
| if (result.getElement() != null) { |
| final PsiClass aClass = result.getElement(); |
| |
| return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory() |
| .createType(aClass, result.getSubstitutor().putAll(qualifierSubs)); |
| } |
| } |
| } |
| } |
| else if (expr instanceof PsiReferenceExpression) { |
| final PsiType type = evaluateReferenceExpressionType(expr); |
| if (type != null) { |
| return PsiImplUtil.normalizeWildcardTypeByPosition(type, expr); |
| } |
| } else if (expr instanceof PsiSuperExpression) { |
| final PsiClass psiClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); |
| if (psiClass != null) { |
| final PsiClass superClass = psiClass.getSuperClass(); |
| if (superClass != null) { |
| return getType(new TypeMigrationUsageInfo(superClass)); |
| } |
| } |
| } |
| |
| return getType(expr); |
| } |
| |
| @Nullable |
| private PsiType evaluateReferenceExpressionType(PsiExpression expr) { |
| final PsiReferenceExpression ref = (PsiReferenceExpression)expr; |
| final PsiExpression qualifier = ref.getQualifierExpression(); |
| |
| if (qualifier == null) { |
| final PsiElement resolvee = ref.resolve(); |
| |
| if (resolvee == null) { |
| return null; |
| } |
| |
| return resolvee instanceof PsiClass ? JavaPsiFacade.getElementFactory(resolvee.getProject()).createType((PsiClass)resolvee, PsiSubstitutor.EMPTY) : getType(resolvee); |
| } |
| else { |
| final PsiType qualifierType = evaluateType(qualifier); |
| if (!(qualifierType instanceof PsiArrayType)) { |
| final PsiElement element = ref.resolve(); |
| |
| final PsiClassType.ClassResolveResult result = resolveType(qualifierType); |
| |
| final PsiClass aClass = result.getElement(); |
| if (aClass != null) { |
| final PsiSubstitutor aSubst = result.getSubstitutor(); |
| if (element instanceof PsiField) { |
| final PsiField field = (PsiField)element; |
| PsiType aType = field.getType(); |
| final PsiClass superClass = field.getContainingClass(); |
| if (InheritanceUtil.isInheritorOrSelf(aClass, superClass, true)) { |
| aType = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY).substitute(aType); |
| } |
| return aSubst.substitute(aType); |
| } else if (element instanceof PsiMethod){ |
| final PsiMethod method = (PsiMethod)element; |
| PsiType aType = method.getReturnType(); |
| final PsiClass superClass = method.getContainingClass(); |
| if (InheritanceUtil.isInheritorOrSelf(aClass, superClass, true)) { |
| aType = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY).substitute(aType); |
| } else if (InheritanceUtil.isInheritorOrSelf(superClass, aClass, true)) { |
| final PsiMethod[] methods = method.findSuperMethods(aClass); |
| if (methods.length > 0) { |
| aType = methods[0].getReturnType(); |
| } |
| /*} |
| final Pair<PsiType, PsiType> pair = myRules.bindTypeParameters(qualifier.getType(), qualifierType, method, ref, myLabeler); |
| if (pair != null) { |
| final PsiClass psiClass = PsiUtil.resolveClassInType(aType); |
| if (psiClass instanceof PsiTypeParameter) { |
| aSubst = aSubst.put((PsiTypeParameter)psiClass, pair.getSecond()); |
| }*/ |
| } |
| return aSubst.substitute(aType); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static PsiClassType.ClassResolveResult resolveType(PsiType type) { |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(type); |
| final PsiClass aClass = resolveResult.getElement(); |
| if (aClass instanceof PsiAnonymousClass) { |
| final PsiClassType baseClassType = ((PsiAnonymousClass)aClass).getBaseClassType(); |
| return resolveType(resolveResult.getSubstitutor().substitute(baseClassType)); |
| } |
| return resolveResult; |
| } |
| |
| public PsiSubstitutor createMethodSubstitution(final PsiParameter[] parameters, final PsiExpression[] actualParms, final PsiMethod method, |
| final PsiExpression call) { |
| return createMethodSubstitution(parameters, actualParms, method, call, PsiSubstitutor.EMPTY, false); |
| } |
| |
| public PsiSubstitutor createMethodSubstitution(final PsiParameter[] parameters, |
| final PsiExpression[] actualParms, |
| final PsiMethod method, |
| final PsiExpression call, |
| PsiSubstitutor subst, |
| boolean preferSubst) { |
| final SubstitutorBuilder substitutorBuilder = new SubstitutorBuilder(method, call, subst); |
| |
| for (int i = 0; i < Math.min(parameters.length, actualParms.length); i++) { |
| substitutorBuilder.bindTypeParameters(getType(parameters[i]), evaluateType(actualParms[i])); |
| } |
| return substitutorBuilder.createSubstitutor(preferSubst); |
| } |
| |
| public String getReport() { |
| final StringBuffer buffer = new StringBuffer(); |
| |
| final String[] t = new String[myTypeMap.size()]; |
| int k = 0; |
| |
| for (final TypeMigrationUsageInfo info : myTypeMap.keySet()) { |
| final LinkedList<PsiType> types = myTypeMap.get(info); |
| final StringBuffer b = new StringBuffer(); |
| |
| if (types != null) { |
| b.append(info.getElement()).append(" : "); |
| |
| b.append(StringUtil.join(types, new Function<PsiType, String>() { |
| public String fun(final PsiType psiType) { |
| return psiType.getCanonicalText(); |
| } |
| }, " ")); |
| |
| b.append("\n"); |
| } |
| |
| t[k++] = b.toString(); |
| } |
| |
| Arrays.sort(t); |
| |
| for (String aT : t) { |
| buffer.append(aT); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| public LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> getMigratedDeclarations() { |
| final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> list = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>(); |
| |
| for (final TypeMigrationUsageInfo usageInfo : myTypeMap.keySet()) { |
| final LinkedList<PsiType> types = myTypeMap.get(usageInfo); |
| final PsiElement element = usageInfo.getElement(); |
| if (element instanceof PsiVariable || element instanceof PsiMethod) { |
| list.addLast(Pair.create(usageInfo, types.getFirst())); |
| } |
| } |
| |
| return list; |
| } |
| |
| @Nullable |
| static PsiType substituteType(final PsiType migrationType, final PsiType originalType, boolean captureWildcard, PsiClass originalClass, final PsiType rawTypeToReplace) { |
| if (originalClass != null) { |
| if (((PsiClassType)originalType).hasParameters() && ((PsiClassType)migrationType).hasParameters()) { |
| final PsiResolveHelper psiResolveHelper = JavaPsiFacade.getInstance(originalClass.getProject()).getResolveHelper(); |
| |
| final PsiType rawOriginalType = JavaPsiFacade.getElementFactory(originalClass.getProject()).createType(originalClass, PsiSubstitutor.EMPTY); |
| |
| PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; |
| for (PsiTypeParameter parameter : originalClass.getTypeParameters()) { |
| final PsiType type = psiResolveHelper.getSubstitutionForTypeParameter(parameter, rawOriginalType, migrationType, false, PsiUtil.getLanguageLevel(originalClass)); |
| if (type != null) { |
| substitutor = substitutor.put(parameter, captureWildcard && type instanceof PsiWildcardType ? ((PsiWildcardType)type).getExtendsBound() : type); |
| } else { |
| return null; |
| } |
| } |
| |
| return substitutor.substitute(rawTypeToReplace); |
| } else { |
| return originalType; |
| } |
| } |
| return null; |
| } |
| |
| public static PsiType substituteType(final PsiType migrationTtype, final PsiType originalType, final boolean isContraVariantPosition) { |
| if ( originalType instanceof PsiClassType && migrationTtype instanceof PsiClassType) { |
| final PsiClass originalClass = ((PsiClassType)originalType).resolve(); |
| if (originalClass != null) { |
| if (isContraVariantPosition && TypeConversionUtil.erasure(originalType).isAssignableFrom(TypeConversionUtil.erasure(migrationTtype))) { |
| final PsiClass psiClass = ((PsiClassType)migrationTtype).resolve(); |
| final PsiSubstitutor substitutor = psiClass != null ? TypeConversionUtil.getClassSubstitutor(originalClass, psiClass, PsiSubstitutor.EMPTY) : null; |
| if (substitutor != null) { |
| final PsiType psiType = |
| substituteType(migrationTtype, originalType, false, psiClass, JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(originalClass, substitutor)); |
| if (psiType != null) { |
| return psiType; |
| } |
| } |
| } |
| else if (!isContraVariantPosition && TypeConversionUtil.erasure(migrationTtype).isAssignableFrom(TypeConversionUtil.erasure(originalType))) { |
| final PsiType psiType = substituteType(migrationTtype, originalType, false, originalClass, JavaPsiFacade.getElementFactory(originalClass.getProject()).createType(originalClass, PsiSubstitutor.EMPTY)); |
| if (psiType != null) { |
| return psiType; |
| } |
| } |
| } |
| } |
| return migrationTtype; |
| } |
| |
| private class SubstitutorBuilder { |
| private final Map<PsiTypeParameter, PsiType> myMapping; |
| private final PsiMethod myMethod; |
| private final PsiExpression myCall; |
| private final PsiSubstitutor mySubst; |
| |
| |
| public SubstitutorBuilder(PsiMethod method, PsiExpression call, PsiSubstitutor subst) { |
| mySubst = subst; |
| myMapping = new HashMap<PsiTypeParameter, PsiType>(); |
| myMethod = method; |
| myCall = call; |
| } |
| |
| private void update(final PsiTypeParameter p, PsiType t) { |
| if (t instanceof PsiPrimitiveType) { |
| t = ((PsiPrimitiveType)t).getBoxedType(myMethod); |
| } |
| final PsiType binding = myMapping.get(p); |
| |
| if (binding == null) { |
| myMapping.put(p, t); |
| } |
| else if (t != null) { |
| myMapping.put(p, PsiIntersectionType.createIntersection(binding, t)); |
| } |
| } |
| |
| void bindTypeParameters(PsiType formal, final PsiType actual) { |
| if (formal instanceof PsiWildcardType) formal = ((PsiWildcardType)formal).getBound(); |
| |
| if (formal instanceof PsiArrayType && actual instanceof PsiArrayType) { |
| bindTypeParameters(((PsiArrayType)formal).getComponentType(), ((PsiArrayType)actual).getComponentType()); |
| return; |
| } |
| |
| final Pair<PsiType, PsiType> typePair = myRules.bindTypeParameters(formal, actual, myMethod, myCall, myLabeler); |
| if (typePair != null) { |
| bindTypeParameters(typePair.getFirst(), typePair.getSecond()); |
| return; |
| } |
| |
| final PsiClassType.ClassResolveResult resultF = resolveType(formal); |
| final PsiClass classF = resultF.getElement(); |
| if (classF != null) { |
| |
| if (classF instanceof PsiTypeParameter) { |
| update((PsiTypeParameter)classF, actual); |
| return; |
| } |
| |
| final PsiClassType.ClassResolveResult resultA = resolveType(actual); |
| |
| final PsiClass classA = resultA.getElement(); |
| if (classA == null) { |
| return; |
| } |
| |
| |
| if (!classA.equals(classF)) { |
| final PsiSubstitutor superClassSubstitutor = |
| TypeConversionUtil.getClassSubstitutor(classF, classA, resultA.getSubstitutor()); |
| if (superClassSubstitutor != null) { |
| final PsiType aligned = JavaPsiFacade.getInstance(classF.getProject()).getElementFactory().createType(classF, superClassSubstitutor); |
| bindTypeParameters(formal, aligned); |
| } |
| } |
| |
| final PsiTypeParameter[] typeParms = classA.getTypeParameters(); |
| final PsiSubstitutor substA = resultA.getSubstitutor(); |
| final PsiSubstitutor substF = resultF.getSubstitutor(); |
| |
| for (PsiTypeParameter typeParm : typeParms) { |
| bindTypeParameters(substF.substitute(typeParm), substA.substitute(typeParm)); |
| } |
| } |
| } |
| |
| public PsiSubstitutor createSubstitutor(boolean preferSubst) { |
| PsiSubstitutor theSubst = mySubst; |
| if (preferSubst) { |
| myMapping.keySet().removeAll(mySubst.getSubstitutionMap().keySet()); |
| } |
| for (final PsiTypeParameter parm : myMapping.keySet()) { |
| theSubst = theSubst.put(parm, myMapping.get(parm)); |
| } |
| return theSubst; |
| } |
| } |
| } |