| /* |
| * 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.lang.java.JavaLanguage; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Messages; |
| 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.javadoc.PsiDocTagValue; |
| import com.intellij.psi.search.PsiSearchScopeUtil; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.OverridingMethodsSearch; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.typeMigration.usageInfo.OverridenUsageInfo; |
| import com.intellij.refactoring.typeMigration.usageInfo.OverriderUsageInfo; |
| import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Query; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import javax.swing.*; |
| import java.util.*; |
| |
| /** |
| * @author db |
| * Date: Sep 19, 2004 |
| */ |
| public class TypeMigrationLabeler { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.typeMigration.TypeMigrationLabeler"); |
| private boolean myShowWarning = true; |
| private MigrateException myException; |
| |
| public TypeMigrationRules getRules() { |
| return myRules; |
| } |
| |
| private final TypeMigrationRules myRules; |
| private TypeEvaluator myTypeEvaluator; |
| private final LinkedHashMap<PsiElement, Object> myConversions; |
| private final HashSet<Pair<PsiAnchor, PsiType>> myFailedConversions; |
| private LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> myMigrationRoots; |
| private final LinkedHashMap<TypeMigrationUsageInfo, PsiType> myNewExpressionTypeChange; |
| private final LinkedHashMap<TypeMigrationUsageInfo, PsiClassType> myClassTypeArgumentsChange; |
| |
| private TypeMigrationUsageInfo[] myMigratedUsages = null; |
| |
| private TypeMigrationUsageInfo myCurrentRoot; |
| private final Map<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> myRootsTree = |
| new HashMap<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>>(); |
| private final Map<Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo>, Set<PsiElement>> myRootUsagesTree = new HashMap<Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo>, Set<PsiElement>>(); |
| private final Set<TypeMigrationUsageInfo> myProcessedRoots = new HashSet<TypeMigrationUsageInfo>(); |
| |
| |
| public TypeMigrationLabeler(final TypeMigrationRules rules) { |
| myRules = rules; |
| |
| myConversions = new LinkedHashMap<PsiElement, Object>(); |
| myFailedConversions = new HashSet<Pair<PsiAnchor, PsiType>>(); |
| myNewExpressionTypeChange = new LinkedHashMap<TypeMigrationUsageInfo, PsiType>(); |
| myClassTypeArgumentsChange = new LinkedHashMap<TypeMigrationUsageInfo, PsiClassType>(); |
| } |
| |
| public boolean hasFailedConversions() { |
| return myFailedConversions.size() > 0; |
| } |
| |
| public String[] getFailedConversionsReport() { |
| final String[] report = new String[myFailedConversions.size()]; |
| int j = 0; |
| |
| for (final Pair<PsiAnchor, PsiType> p : myFailedConversions) { |
| final PsiElement element = p.getFirst().retrieve(); |
| LOG.assertTrue(element != null); |
| final PsiType type = ((PsiExpression)element).getType(); |
| report[j++] = "Cannot convert type of expression <b>" + StringUtil.escapeXml(element.getText()) + "</b>" + |
| (type != null |
| ? " from <b>" + StringUtil.escapeXml(type.getCanonicalText()) + "</b>" + |
| " to <b>" + StringUtil.escapeXml(p.getSecond().getCanonicalText()) + "</b>" |
| : "") |
| + "<br>"; |
| } |
| |
| return report; |
| } |
| |
| public UsageInfo[] getFailedUsages() { |
| final List<UsageInfo> usages = new ArrayList<UsageInfo>(myFailedConversions.size()); |
| for (final Pair<PsiAnchor, PsiType> p : myFailedConversions) { |
| final PsiExpression expr = (PsiExpression)p.getFirst().retrieve(); |
| if (expr != null) { |
| usages.add(new UsageInfo(expr) { |
| @Nullable |
| public String getTooltipText() { |
| final PsiType type = expr.isValid() ? expr.getType() : null; |
| if (type == null) return null; |
| return "Cannot convert type of the expression from " + |
| type.getCanonicalText() + " to " + p.getSecond().getCanonicalText(); |
| } |
| }); |
| } |
| } |
| |
| return usages.toArray(new UsageInfo[usages.size()]); |
| } |
| |
| public TypeMigrationUsageInfo[] getMigratedUsages() { |
| final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> declarations = getTypeEvaluator().getMigratedDeclarations(); |
| final TypeMigrationUsageInfo[] usages = new TypeMigrationUsageInfo[declarations.size() + myConversions.size() + myNewExpressionTypeChange.size() + myClassTypeArgumentsChange.size()]; |
| |
| int j = 0; |
| |
| List<PsiElement> conversionExprs = new ArrayList<PsiElement>(myConversions.keySet()); |
| Collections.sort(conversionExprs, new Comparator<PsiElement>() { |
| public int compare(final PsiElement e1, final PsiElement e2) { |
| return e2.getTextRange().getStartOffset() - e1.getTextRange().getStartOffset(); |
| } |
| }); |
| for (final PsiElement element : conversionExprs) { |
| final Object conv = myConversions.get(element); |
| |
| usages[j++] = new TypeMigrationUsageInfo(element) { |
| public String getTooltipText() { |
| if (conv instanceof String) { //todo |
| final String conversion = (String)conv; |
| return "Replaced with " + conversion.replaceAll("\\$", element.getText()); |
| } |
| else { |
| return "Replaced with " + conv.toString(); |
| } |
| } |
| |
| @Override |
| public boolean isExcluded() { |
| if (conv instanceof TypeConversionDescriptorBase) return ((TypeConversionDescriptorBase)conv).getRoot().isExcluded(); |
| return super.isExcluded(); |
| } |
| }; |
| } |
| |
| for (final Pair<TypeMigrationUsageInfo, PsiType> p : declarations) { |
| final TypeMigrationUsageInfo element = p.getFirst(); |
| usages[j++] = element; |
| } |
| |
| for (TypeMigrationUsageInfo info : myClassTypeArgumentsChange.keySet()) { |
| usages[j++] = info; |
| } |
| |
| for (final TypeMigrationUsageInfo expr : myNewExpressionTypeChange.keySet()) { |
| usages[j++] = expr; |
| } |
| return usages; |
| } |
| |
| public void change(final TypeMigrationUsageInfo usageInfo) { |
| final PsiElement element = usageInfo.getElement(); |
| if (element == null) return; |
| final Project project = element.getProject(); |
| if (element instanceof PsiExpression) { |
| final PsiExpression expression = (PsiExpression)element; |
| if (element instanceof PsiNewExpression) { |
| for (Map.Entry<TypeMigrationUsageInfo, PsiType> info : myNewExpressionTypeChange.entrySet()) { |
| final PsiElement expressionToReplace = info.getKey().getElement(); |
| if (expression.equals(expressionToReplace)) { |
| TypeMigrationReplacementUtil.replaceNewExpressionType(project, (PsiNewExpression)expressionToReplace, info); |
| } |
| } |
| } |
| final Object conversion = myConversions.get(element); |
| if (conversion != null) { |
| myConversions.remove(element); |
| TypeMigrationReplacementUtil.replaceExpression(expression, project, conversion); |
| } |
| } else if (element instanceof PsiReferenceParameterList) { |
| for (Map.Entry<TypeMigrationUsageInfo, PsiClassType> entry : myClassTypeArgumentsChange.entrySet()) { |
| if (element.equals(entry.getKey().getElement())) { //todo check null |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); |
| try { |
| element.getParent().replace(factory.createReferenceElementByType(entry.getValue())); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| } |
| else { |
| TypeMigrationReplacementUtil.migratePsiMemberType(element, project, getTypeEvaluator().getType(usageInfo)); |
| } |
| } |
| |
| @Nullable |
| Object getConversion(PsiElement element) { |
| return myConversions.get(element); |
| } |
| |
| public TypeMigrationUsageInfo[] getMigratedUsages(boolean autoMigrate, final PsiElement... roots) { |
| if (myMigratedUsages == null) { |
| myShowWarning = autoMigrate; |
| migrate(autoMigrate, roots); |
| myMigratedUsages = getMigratedUsages(); |
| } |
| return myMigratedUsages; |
| } |
| |
| @Nullable |
| public Set<PsiElement> getTypeUsages(final TypeMigrationUsageInfo element, final TypeMigrationUsageInfo currentRoot) { |
| return myRootUsagesTree.get(Pair.create(element, currentRoot)); |
| } |
| |
| void convertExpression(final PsiExpression expr, final PsiType toType, final PsiType fromType, final boolean isCovariantPosition) { |
| final TypeConversionDescriptorBase conversion = myRules.findConversion(fromType, toType, expr instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)expr).resolveMethod() : null, expr, |
| isCovariantPosition, this); |
| |
| if (conversion == null) { |
| markFailedConversion(Pair.create(fromType, toType), expr); |
| } |
| else { |
| setConversionMapping(expr, conversion); |
| } |
| } |
| |
| public void migrateExpressionType(final PsiExpression expr, final PsiType migrationType, final PsiElement place, boolean alreadyProcessed, final boolean isCovariant) { |
| PsiType originalType = expr.getType(); |
| |
| if (originalType == null || originalType.equals(migrationType)) return; |
| |
| if (originalType.equals(PsiType.NULL)) { |
| if (migrationType instanceof PsiPrimitiveType) { |
| markFailedConversion(Pair.create(originalType, migrationType), expr); |
| } |
| return; |
| } |
| |
| if (expr instanceof PsiConditionalExpression) { |
| |
| } else if (expr instanceof PsiClassObjectAccessExpression) { |
| if (!TypeConversionUtil.isAssignable(migrationType, expr.getType())) { |
| markFailedConversion(Pair.create(expr.getType(), migrationType), expr); |
| return; |
| } |
| } else if (expr instanceof PsiArrayInitializerExpression && migrationType instanceof PsiArrayType) { |
| final PsiExpression[] initializers = ((PsiArrayInitializerExpression)expr).getInitializers(); |
| for (PsiExpression initializer : initializers) { |
| migrateExpressionType(initializer, ((PsiArrayType)migrationType).getComponentType(), expr, alreadyProcessed, true); |
| } |
| getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType); |
| return; |
| } else if (expr instanceof PsiArrayAccessExpression) { |
| migrateExpressionType(((PsiArrayAccessExpression)expr).getArrayExpression(), migrationType.createArrayType(), place, alreadyProcessed, isCovariant); |
| return; |
| } |
| else if (expr instanceof PsiReferenceExpression) { |
| final PsiElement resolved = ((PsiReferenceExpression)expr).resolve(); |
| if (resolved != null) { |
| if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) { |
| convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant); |
| } |
| } |
| return; |
| } |
| else if (expr instanceof PsiMethodCallExpression) { |
| final PsiMethod resolved = ((PsiMethodCallExpression)expr).resolveMethod(); |
| if (resolved != null) { |
| if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) { |
| convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant); |
| } |
| } |
| return; |
| } |
| else if (expr instanceof PsiNewExpression) { |
| if (originalType.getArrayDimensions() == migrationType.getArrayDimensions()) { |
| if (migrationType.getArrayDimensions() > 0) { |
| final PsiType elemenType = ((PsiArrayType)migrationType).getComponentType(); |
| |
| final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)expr).getArrayInitializer(); |
| |
| if (arrayInitializer != null) { |
| final PsiExpression[] initializers = arrayInitializer.getInitializers(); |
| for (int i = initializers.length - 1; i >= 0; i--) { |
| migrateExpressionType(initializers[i], elemenType, place, alreadyProcessed, true); |
| } |
| } |
| |
| if (isGenericsArrayType(elemenType)){ |
| markFailedConversion(Pair.create(originalType, migrationType), expr); |
| return; |
| } |
| |
| myNewExpressionTypeChange.put(new TypeMigrationUsageInfo(expr), migrationType); |
| getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType); |
| return; |
| } else { |
| if (migrationType instanceof PsiClassType && originalType instanceof PsiClassType && ((PsiClassType)migrationType).rawType().isAssignableFrom(((PsiClassType)originalType).rawType())) { |
| final PsiClass originalClass = PsiUtil.resolveClassInType(originalType); |
| if (originalClass instanceof PsiAnonymousClass) { |
| originalType = ((PsiAnonymousClass)originalClass).getBaseClassType(); |
| } |
| final PsiType type = TypeEvaluator.substituteType(migrationType, originalType, true, ((PsiClassType)originalType).resolveGenerics().getElement(), |
| JavaPsiFacade.getElementFactory(expr.getProject()).createType(((PsiClassType)originalType).resolve(), PsiSubstitutor.EMPTY)); |
| if (type != null){ |
| myNewExpressionTypeChange.put(new TypeMigrationUsageInfo(expr), type); |
| getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), type); |
| return; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| convertExpression(expr, migrationType, originalType, isCovariant); |
| } |
| |
| private static boolean isGenericsArrayType(final PsiType elemenType) { |
| if (elemenType instanceof PsiClassType && ((PsiClassType)elemenType).hasParameters()) { |
| return true; |
| } else if (elemenType instanceof PsiArrayType) { |
| final PsiType componentType = ((PsiArrayType)elemenType).getComponentType(); |
| return isGenericsArrayType(componentType); |
| } |
| return false; |
| } |
| |
| boolean addMigrationRoot(PsiElement element, PsiType type, final PsiElement place, boolean alreadyProcessed, final boolean isContraVariantPosition) { |
| return addMigrationRoot(element, type, place, alreadyProcessed, isContraVariantPosition, false); |
| } |
| |
| boolean addMigrationRoot(PsiElement element, |
| PsiType type, |
| final PsiElement place, |
| boolean alreadyProcessed, |
| final boolean isContraVariantPosition, |
| final boolean userDefinedType) { |
| if (type.equals(PsiType.NULL)) { |
| return false; |
| } |
| |
| final PsiElement resolved = Util.normalizeElement(element); |
| |
| final SearchScope searchScope = myRules.getSearchScope(); |
| if (!resolved.isPhysical() || !PsiSearchScopeUtil.isInScope(searchScope, resolved)) { |
| return false; |
| } |
| |
| final PsiType originalType = getElementType(resolved); |
| |
| LOG.assertTrue(originalType != null); |
| |
| type = userDefinedType ? type : TypeEvaluator.substituteType(type, originalType, isContraVariantPosition); |
| |
| if (!userDefinedType) { |
| if (typeContainsTypeParameters(originalType)) return false; |
| } |
| |
| if (type instanceof PsiCapturedWildcardType) { |
| return false; |
| } |
| |
| if (resolved instanceof PsiMethod) { |
| final PsiMethod method = ((PsiMethod)resolved); |
| final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY); |
| final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method); |
| final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length]; |
| for (int i = -1; i < methods.length; i++) { |
| final TypeMigrationUsageInfo m; |
| if (i < 0) { |
| m = overridenUsageInfo; |
| } |
| else { |
| overriders[i] = new OverriderUsageInfo(methods[i], method); |
| m = overriders[i]; |
| } |
| |
| alreadyProcessed = addRoot(m, type, place, alreadyProcessed); |
| } |
| overridenUsageInfo.setOverriders(overriders); |
| |
| return !alreadyProcessed; |
| } |
| else if (resolved instanceof PsiParameter && ((PsiParameter)resolved).getDeclarationScope() instanceof PsiMethod) { |
| final PsiMethod method = (PsiMethod)((PsiParameter)resolved).getDeclarationScope(); |
| |
| final int index = method.getParameterList().getParameterIndex(((PsiParameter)resolved)); |
| final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY); |
| |
| final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length]; |
| final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method.getParameterList().getParameters()[index]); |
| for (int i = -1; i < methods.length; i++) { |
| final PsiMethod m = i < 0 ? method : methods[i]; |
| final PsiParameter p = m.getParameterList().getParameters()[index]; |
| final TypeMigrationUsageInfo paramUsageInfo; |
| if (i < 0) { |
| paramUsageInfo = overridenUsageInfo; |
| } |
| else { |
| overriders[i] = new OverriderUsageInfo(p, method); |
| paramUsageInfo = overriders[i]; |
| } |
| alreadyProcessed = addRoot(paramUsageInfo, type, place, alreadyProcessed); |
| } |
| |
| overridenUsageInfo.setOverriders(overriders); |
| |
| return !alreadyProcessed; |
| } |
| else { |
| return !addRoot(new TypeMigrationUsageInfo(resolved), type, place, alreadyProcessed); |
| } |
| } |
| |
| static boolean typeContainsTypeParameters(PsiType originalType) { |
| if (originalType instanceof PsiClassType) { |
| final PsiClassType psiClassType = (PsiClassType)originalType; |
| if (psiClassType.resolve() instanceof PsiTypeParameter) { |
| return true; |
| } |
| for (PsiType paramType : psiClassType.getParameters()) { |
| if (paramType instanceof PsiClassType && ((PsiClassType)paramType).resolve() instanceof PsiTypeParameter) return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| @Nullable |
| public static PsiType getElementType(final PsiElement resolved) { |
| if (resolved instanceof PsiVariable) { |
| return ((PsiVariable)resolved).getType(); |
| } |
| else { |
| if (resolved instanceof PsiMethod) { |
| return (((PsiMethod)resolved).getReturnType()); |
| } |
| else if (resolved instanceof PsiExpression){ |
| return (((PsiExpression)resolved).getType()); |
| } else if (resolved instanceof PsiReferenceParameterList) { |
| PsiElement parent = resolved.getParent(); |
| while (parent != null) { |
| LOG.assertTrue(parent instanceof PsiJavaCodeReferenceElement); |
| final PsiClass psiClass = (PsiClass)((PsiJavaCodeReferenceElement)parent).resolve(); |
| final PsiClass containingClass = PsiTreeUtil.getParentOfType(parent, PsiClass.class); |
| if (psiClass != null && containingClass != null) { |
| final PsiSubstitutor classSubstitutor = TypeConversionUtil.getClassSubstitutor(psiClass, containingClass, PsiSubstitutor.EMPTY); |
| if (classSubstitutor != null) { |
| return JavaPsiFacade.getElementFactory(parent.getProject()).createType(psiClass, classSubstitutor); |
| } |
| } |
| parent = PsiTreeUtil.getParentOfType(parent, PsiJavaCodeReferenceElement.class, true); |
| } |
| } else if (resolved instanceof PsiClass) { |
| return JavaPsiFacade.getElementFactory(resolved.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY); |
| } |
| } |
| return null; |
| } |
| |
| boolean addRoot(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, boolean alreadyProcessed) { |
| if (myShowWarning && myMigrationRoots.size() > 10 && !ApplicationManager.getApplication().isUnitTestMode()) { |
| myShowWarning = false; |
| try { |
| final Runnable checkTimeToStopRunnable = new Runnable() { |
| public void run() { |
| if (Messages.showYesNoCancelDialog("Found more than 10 roots to migrate. Do you want to preview?", "Type Migration", |
| Messages.getWarningIcon()) == Messages.YES) { |
| myException = new MigrateException(); |
| } |
| } |
| }; |
| SwingUtilities.invokeLater(checkTimeToStopRunnable); |
| } |
| catch (Exception e) { |
| //do nothing |
| } |
| } |
| if (myException != null) throw myException; |
| rememberRootTrace(usageInfo, type, place, alreadyProcessed); |
| if (!alreadyProcessed && !getTypeEvaluator().setType(usageInfo, type)) { |
| alreadyProcessed = true; |
| } |
| |
| if (!alreadyProcessed) myMigrationRoots.addFirst(Pair.create(usageInfo, type)); |
| return alreadyProcessed; |
| } |
| |
| private void rememberRootTrace(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, final boolean alreadyProcessed) { |
| if (myCurrentRoot != null) { |
| if (!alreadyProcessed) { |
| myProcessedRoots.add(usageInfo); |
| } |
| |
| if (myProcessedRoots.contains(usageInfo)) { |
| HashSet<Pair<TypeMigrationUsageInfo, PsiType>> infos = myRootsTree.get(myCurrentRoot); |
| if (infos == null) { |
| infos = new HashSet<Pair<TypeMigrationUsageInfo, PsiType>>(); |
| myRootsTree.put(myCurrentRoot, infos); |
| } |
| infos.add(Pair.create(usageInfo, type)); |
| } |
| if (!(usageInfo instanceof OverriderUsageInfo)) { //hide the same usage for all overriders |
| setTypeUsage(usageInfo, place); |
| } |
| } |
| } |
| |
| private void setTypeUsage(final TypeMigrationUsageInfo usageInfo, final PsiElement place) { |
| if (place != null) { |
| final Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo> rooted = Pair.create(usageInfo, myCurrentRoot); |
| Set<PsiElement> usages = myRootUsagesTree.get(rooted); |
| if (usages == null) { |
| usages = new HashSet<PsiElement>(); |
| myRootUsagesTree.put(rooted, usages); |
| } |
| usages.add(place); |
| } |
| } |
| |
| public void setTypeUsage(final PsiElement element, final PsiElement place) { |
| setTypeUsage(new TypeMigrationUsageInfo(element), place); |
| } |
| |
| void markFailedConversion(final Pair<PsiType, PsiType> typePair, final PsiExpression expression) { |
| LOG.assertTrue(typePair.getSecond() != null); |
| myFailedConversions.add(Pair.create(PsiAnchor.create(expression), typePair.getSecond())); |
| } |
| |
| void setConversionMapping(final PsiExpression expression, final Object obj) { |
| if (myConversions.get(expression) != null) { |
| return; |
| } |
| |
| if (obj instanceof TypeConversionDescriptorBase) { |
| ((TypeConversionDescriptorBase)obj).setRoot(myCurrentRoot); |
| } |
| myConversions.put(expression, obj); |
| } |
| |
| public PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType) { |
| return markRootUsages(element, migrationType, ReferencesSearch.search(element, myRules.getSearchScope(), false).toArray(new PsiReference[0])); |
| } |
| |
| PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType, final PsiReference[] refs) { |
| final List<PsiReference> validReferences = new ArrayList<PsiReference>(); |
| for (PsiReference ref1 : refs) { |
| final PsiElement ref = ref1.getElement(); |
| |
| if (ref != null) { |
| if (element instanceof PsiMethod) { |
| final PsiElement parent = Util.getEssentialParent(ref); |
| |
| if (!(parent instanceof PsiMethodCallExpression)) { |
| continue; |
| } |
| |
| getTypeEvaluator().setType(new TypeMigrationUsageInfo(parent), migrationType); |
| } |
| else if (element instanceof PsiVariable) { |
| if (ref instanceof PsiReferenceExpression) { |
| getTypeEvaluator().setType(new TypeMigrationUsageInfo(ref), PsiImplUtil.normalizeWildcardTypeByPosition(migrationType, (PsiReferenceExpression)ref)); |
| } |
| } |
| else { |
| LOG.error("Method call expression or reference expression expected but found " + element.getClass().getName()); |
| continue; |
| } |
| validReferences.add(ref1); |
| } |
| } |
| |
| Collections.sort(validReferences, new Comparator<PsiReference>() { |
| public int compare(final PsiReference o1, final PsiReference o2) { |
| return o1.getElement().getTextOffset() - o2.getElement().getTextOffset(); |
| } |
| }); |
| |
| return validReferences.toArray(new PsiReference[validReferences.size()]); |
| } |
| |
| public void migrateRoot(final PsiElement root, final PsiType migrationType, final PsiReference[] usages) { |
| if (root instanceof PsiMethod) { |
| migrateMethodReturnExpression(migrationType, (PsiMethod)root); |
| } |
| else if (root instanceof PsiParameter && ((PsiParameter)root).getDeclarationScope() instanceof PsiMethod) { |
| migrateMethodCallExpressions(migrationType, (PsiParameter)root, null); |
| } |
| else if (root instanceof PsiVariable || root instanceof PsiExpression){ |
| final PsiElement element = getContainingStatement(root); |
| element.accept(new TypeMigrationStatementProcessor(element, this)); |
| } else if (root instanceof PsiReferenceParameterList) { |
| myClassTypeArgumentsChange.put(new TypeMigrationUsageInfo(root), (PsiClassType)migrationType); |
| new ClassTypeArgumentMigrationProcessor(this).migrateClassTypeParameter((PsiReferenceParameterList)root, migrationType); |
| } |
| |
| final Set<PsiElement> processed = new HashSet<PsiElement>(); |
| for (PsiReference usage : usages) { |
| migrateRootUsageExpression(usage, processed); |
| } |
| } |
| |
| private static PsiElement getContainingStatement(final PsiElement root) { |
| final PsiStatement statement = PsiTreeUtil.getParentOfType(root, PsiStatement.class); |
| final PsiField field = PsiTreeUtil.getParentOfType(root, PsiField.class); |
| return statement != null ? statement : field != null ? field : root; |
| } |
| |
| void migrateRootUsageExpression(final PsiReference usage, final Set<PsiElement> processed) { |
| final PsiElement ref = usage.getElement(); |
| if (ref != null && ref.getLanguage() == JavaLanguage.INSTANCE) { |
| final PsiElement element = getContainingStatement(ref); |
| if (element != null && !processed.contains(element)) { |
| processed.add(element); |
| element.accept(new TypeMigrationStatementProcessor(ref, this)); |
| } |
| } |
| } |
| |
| void migrateMethodCallExpressions(final PsiType migrationType, final PsiParameter param, final PsiClass psiClass) { |
| boolean checkNumberOfArguments = false; |
| if (param.getType() instanceof PsiEllipsisType && !(migrationType instanceof PsiEllipsisType)) { |
| checkNumberOfArguments = true; |
| } |
| final PsiType strippedType = |
| migrationType instanceof PsiEllipsisType ? ((PsiEllipsisType)migrationType).getComponentType() : migrationType; |
| final PsiMethod method = (PsiMethod)param.getDeclarationScope(); |
| final PsiParameterList parameterList = method.getParameterList(); |
| final int parametersCount = parameterList.getParametersCount(); |
| final int index = parameterList.getParameterIndex(param); |
| final List<PsiReference> refs = filterReferences(psiClass, ReferencesSearch.search(method, method.getUseScope().intersectWith(myRules.getSearchScope()), false)); |
| for (PsiReference ref1 : refs) { |
| final PsiElement ref = ref1.getElement(); |
| final PsiElement parent = Util.getEssentialParent(ref); |
| if (parent instanceof PsiCallExpression) { |
| final PsiExpressionList argumentList = ((PsiCallExpression)parent).getArgumentList(); |
| if (argumentList != null) { |
| final PsiExpression[] expressions = argumentList.getExpressions(); |
| if (checkNumberOfArguments && parametersCount != expressions.length) { |
| markFailedConversion(Pair.create(param.getType(), migrationType), (PsiCallExpression)parent); |
| } |
| if (index > -1 && index < expressions.length) { |
| for (int idx = index; idx < (param.isVarArgs() ? expressions.length : index + 1); idx++) { |
| final PsiExpression actual = expressions[idx]; |
| final PsiType type = getTypeEvaluator().evaluateType(actual); |
| if (type != null) { |
| migrateExpressionType(actual, strippedType, parent, TypeConversionUtil.isAssignable(strippedType, type), true); |
| } |
| } |
| } |
| } |
| } else if (ref instanceof PsiDocTagValue) { |
| myConversions.put(ref, method); |
| } |
| } |
| } |
| |
| private void migrateMethodReturnExpression(final PsiType migrationType, final PsiMethod method) { |
| final PsiCodeBlock block = method.getBody(); |
| if (block != null) { |
| block.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitReturnStatement(PsiReturnStatement statement) { |
| final PsiExpression value = statement.getReturnValue(); |
| if (value != null) { |
| final PsiType type = getTypeEvaluator().evaluateType(value); |
| if (type != null && !type.equals(migrationType)) { |
| migrateExpressionType(value, migrationType, statement, TypeConversionUtil.isAssignable(migrationType, type), true); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| private void iterate() { |
| final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> roots = |
| (LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>)myMigrationRoots.clone(); |
| |
| myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>(); |
| |
| final PsiReference[][] cachedUsages = new PsiReference[roots.size()][]; |
| int j = 0; |
| |
| for (final Pair<TypeMigrationUsageInfo, PsiType> p : roots) { |
| cachedUsages[j++] = markRootUsages(p.getFirst().getElement(), p.getSecond()); |
| } |
| |
| j = 0; |
| |
| for (final Pair<TypeMigrationUsageInfo, PsiType> root : roots) { |
| myCurrentRoot = root.getFirst(); |
| migrateRoot(root.getFirst().getElement(), root.getSecond(), cachedUsages[j++]); |
| } |
| } |
| |
| private void migrate(boolean autoMigrate, final PsiElement... victims) { |
| myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>(); |
| myTypeEvaluator = new TypeEvaluator(myMigrationRoots, this); |
| |
| |
| final PsiType rootType = myRules.getMigrationRootType(); |
| for (PsiElement victim : victims) { |
| addMigrationRoot(victim, rootType, null, false, true, true); |
| } |
| |
| if (autoMigrate) { |
| while (myMigrationRoots.size() > 0) { |
| iterate(); |
| } |
| } |
| } |
| |
| public TypeEvaluator getTypeEvaluator() { |
| return myTypeEvaluator; |
| } |
| |
| public Map<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> getRootsTree() { |
| return myRootsTree; |
| } |
| |
| public void setCurrentRoot(final TypeMigrationUsageInfo currentRoot) { |
| myCurrentRoot = currentRoot; |
| } |
| |
| public LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> getMigrationRoots() { |
| return myMigrationRoots; |
| } |
| |
| public static List<PsiReference> filterReferences(final PsiClass psiClass, final Query<PsiReference> memberReferences) { |
| final List<PsiReference> refs = new ArrayList<PsiReference>(); |
| for (PsiReference memberReference : memberReferences) { |
| if (psiClass == null) { |
| refs.add(memberReference); |
| } else { |
| final PsiElement referencedElement = memberReference.getElement(); |
| if (referencedElement instanceof PsiReferenceExpression) { |
| final PsiExpression qualifierExpression = ((PsiReferenceExpression)referencedElement).getQualifierExpression(); |
| if (qualifierExpression != null) { |
| final PsiType qualifierType = qualifierExpression.getType(); |
| if (qualifierType instanceof PsiClassType && psiClass == ((PsiClassType)qualifierType).resolve()) { |
| refs.add(memberReference); |
| } |
| } else { |
| if (psiClass == PsiTreeUtil.getParentOfType(referencedElement, PsiClass.class)) { |
| refs.add(memberReference); |
| } |
| } |
| } |
| } |
| } |
| return refs; |
| } |
| |
| @TestOnly |
| public String getMigrationReport() { |
| final StringBuilder buffer = new StringBuilder(); |
| |
| buffer.append("Types:\n").append(getTypeEvaluator().getReport()).append("\n"); |
| |
| buffer.append("Conversions:\n"); |
| |
| final String[] conversions = new String[myConversions.size()]; |
| int k = 0; |
| |
| for (final PsiElement expr : myConversions.keySet()) { |
| final Object conversion = myConversions.get(expr); |
| |
| if (conversion instanceof Pair && ((Pair)conversion).first == null) { |
| conversions[k++] = (expr.getText() + " -> " + ((Pair)conversion).second + "\n"); |
| } else { |
| conversions[k++] = (expr.getText() + " -> " + conversion + "\n"); |
| } |
| } |
| |
| Arrays.sort(conversions, new Comparator<String>() { |
| public int compare(String x, String y) { |
| return x.compareTo(y); |
| } |
| }); |
| |
| for (String conversion : conversions) { |
| buffer.append(conversion); |
| } |
| |
| buffer.append("\nNew expression type changes:\n"); |
| |
| final String[] newChanges = new String[myNewExpressionTypeChange.size()]; |
| k = 0; |
| |
| for (final Map.Entry<TypeMigrationUsageInfo, PsiType> entry : myNewExpressionTypeChange.entrySet()) { |
| final PsiElement element = entry.getKey().getElement(); |
| newChanges[k++] = (element != null ? element.getText() : entry.getKey()) + " -> " + entry.getValue().getCanonicalText() + "\n"; |
| } |
| |
| Arrays.sort(newChanges, new Comparator<String>() { |
| public int compare(String x, String y) { |
| return x.compareTo(y); |
| } |
| }); |
| |
| for (String change : newChanges) { |
| buffer.append(change); |
| } |
| |
| buffer.append("Fails:\n"); |
| |
| final ArrayList<Pair<PsiAnchor, PsiType>> failsList = new ArrayList<Pair<PsiAnchor, PsiType>>(myFailedConversions); |
| Collections.sort(failsList, new Comparator<Pair<PsiAnchor, PsiType>>() { |
| public int compare(final Pair<PsiAnchor, PsiType> o1, final Pair<PsiAnchor, PsiType> o2) { |
| final PsiElement element1 = o1.getFirst().retrieve(); |
| final PsiElement element2 = o2.getFirst().retrieve(); |
| if (element1 == null || element2 == null) return 0; |
| return element1.getText().compareTo(element2.getText()); |
| } |
| }); |
| |
| for (final Pair<PsiAnchor, PsiType> p : failsList) { |
| final PsiElement element = p.getFirst().retrieve(); |
| if (element != null) { |
| buffer.append(element.getText()).append("->").append(p.getSecond().getCanonicalText()).append("\n"); |
| } |
| } |
| |
| return buffer.toString(); |
| } |
| |
| public static class MigrateException extends RuntimeException { } |
| } |