blob: 274fbd52f6219e732bb1ae74c74eefa7b6a41af2 [file] [log] [blame]
/*
* 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 { }
}