type migration: failed conversions PsiAnchor -> SmartPsiElementPointer (IDEA-147945)
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / typeMigration / TypeMigrationLabeler.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.refactoring.typeMigration;
17
18 import com.intellij.lang.java.JavaLanguage;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.Messages;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.psi.*;
26 import com.intellij.psi.impl.PsiImplUtil;
27 import com.intellij.psi.javadoc.PsiDocTagValue;
28 import com.intellij.psi.search.PsiSearchScopeUtil;
29 import com.intellij.psi.search.SearchScope;
30 import com.intellij.psi.search.searches.OverridingMethodsSearch;
31 import com.intellij.psi.search.searches.ReferencesSearch;
32 import com.intellij.psi.util.PsiTreeUtil;
33 import com.intellij.psi.util.PsiUtil;
34 import com.intellij.psi.util.TypeConversionUtil;
35 import com.intellij.refactoring.typeMigration.usageInfo.OverridenUsageInfo;
36 import com.intellij.refactoring.typeMigration.usageInfo.OverriderUsageInfo;
37 import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo;
38 import com.intellij.usageView.UsageInfo;
39 import com.intellij.util.Consumer;
40 import com.intellij.util.Function;
41 import com.intellij.util.IncorrectOperationException;
42 import com.intellij.util.Query;
43 import com.intellij.util.containers.*;
44 import com.intellij.util.graph.DFSTBuilder;
45 import com.intellij.util.graph.GraphGenerator;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48 import org.jetbrains.annotations.TestOnly;
49
50 import javax.swing.*;
51 import java.util.*;
52 import java.util.HashMap;
53 import java.util.HashSet;
54
55 /**
56  * @author db
57  * Date: Sep 19, 2004
58  */
59 public class TypeMigrationLabeler {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.typeMigration.TypeMigrationLabeler");
61   private boolean myShowWarning = true;
62   private MigrateException myException;
63
64   public TypeMigrationRules getRules() {
65     return myRules;
66   }
67
68   private final TypeMigrationRules myRules;
69   private TypeEvaluator myTypeEvaluator;
70   private final LinkedHashMap<PsiElement, Object> myConversions;
71   private final HashSet<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>> myFailedConversions;
72   private LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> myMigrationRoots;
73   private final LinkedHashMap<TypeMigrationUsageInfo, PsiType> myNewExpressionTypeChange;
74   private final LinkedHashMap<TypeMigrationUsageInfo, PsiClassType> myClassTypeArgumentsChange;
75
76   private TypeMigrationUsageInfo[] myMigratedUsages = null;
77
78   private TypeMigrationUsageInfo myCurrentRoot;
79   private final Map<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> myRootsTree =
80       new HashMap<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>>();
81   private final Map<Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo>, Set<PsiElement>> myRootUsagesTree = new HashMap<Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo>, Set<PsiElement>>();
82   private final Set<TypeMigrationUsageInfo> myProcessedRoots = new HashSet<TypeMigrationUsageInfo>();
83
84
85   public TypeMigrationLabeler(final TypeMigrationRules rules) {
86     myRules = rules;
87     
88     myConversions = new LinkedHashMap<PsiElement, Object>();
89     myFailedConversions = new HashSet<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>>();
90     myNewExpressionTypeChange = new LinkedHashMap<TypeMigrationUsageInfo, PsiType>();
91     myClassTypeArgumentsChange = new LinkedHashMap<TypeMigrationUsageInfo, PsiClassType>();
92   }
93
94   public boolean hasFailedConversions() {
95     return myFailedConversions.size() > 0;
96   }
97
98   public String[] getFailedConversionsReport() {
99     final String[] report = new String[myFailedConversions.size()];
100     int j = 0;
101
102     for (final Pair<SmartPsiElementPointer<PsiExpression>, PsiType> p : myFailedConversions) {
103       final PsiExpression element = p.getFirst().getElement();
104       LOG.assertTrue(element != null);
105       final PsiType type = element.getType();
106       report[j++] = "Cannot convert type of expression <b>" + StringUtil.escapeXml(element.getText()) + "</b>" +
107                     (type != null
108                      ? " from <b>" + StringUtil.escapeXml(type.getCanonicalText()) + "</b>" +
109                        " to <b>" + StringUtil.escapeXml(p.getSecond().getCanonicalText()) + "</b>"
110                      : "")
111                     + "<br>";
112     }
113
114     return report;
115   }
116
117   public UsageInfo[] getFailedUsages() {
118     final List<UsageInfo> usages = new ArrayList<UsageInfo>(myFailedConversions.size());
119     for (final Pair<SmartPsiElementPointer<PsiExpression>, PsiType> p : myFailedConversions) {
120       final PsiExpression expr = p.getFirst().getElement();
121       if (expr != null) {
122         usages.add(new UsageInfo(expr) {
123           @Nullable
124           public String getTooltipText() {
125             final PsiType type = expr.isValid() ? expr.getType() : null;
126             if (type == null) return null;
127             return "Cannot convert type of the expression from " +
128                    type.getCanonicalText() + " to " + p.getSecond().getCanonicalText();
129           }
130         });
131       }
132     }
133
134     return usages.toArray(new UsageInfo[usages.size()]);
135   }
136
137   public TypeMigrationUsageInfo[] getMigratedUsages() {
138     final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> declarations = getTypeEvaluator().getMigratedDeclarations();
139     final TypeMigrationUsageInfo[] usages = new TypeMigrationUsageInfo[declarations.size() + myConversions.size() + myNewExpressionTypeChange.size() + myClassTypeArgumentsChange.size()];
140
141     int j = 0;
142
143     for (final PsiElement element : myConversions.keySet()) {
144       final Object conv = myConversions.get(element);
145       usages[j++] = new TypeMigrationUsageInfo(element) {
146         public String getTooltipText() {
147           if (conv instanceof String) {   //todo
148             final String conversion = (String)conv;
149             return "Replaced with " + conversion.replaceAll("\\$", element.getText());
150           }
151           else {
152             return "Replaced with " + conv.toString();
153           }
154         }
155
156         @Override
157         public boolean isExcluded() {
158           if (conv instanceof TypeConversionDescriptorBase) return ((TypeConversionDescriptorBase)conv).getRoot().isExcluded();
159           return super.isExcluded();
160         }
161
162         @Override
163         public TypeMigrationUsageInfo getOwnerRoot() {
164           return conv instanceof TypeConversionDescriptorBase ? ((TypeConversionDescriptorBase)conv).getRoot() : null;
165         }
166       };
167     }
168
169     for (final Pair<TypeMigrationUsageInfo, PsiType> p : declarations) {
170       final TypeMigrationUsageInfo element = p.getFirst();
171       usages[j++] = element;
172     }
173
174     for (TypeMigrationUsageInfo info : myClassTypeArgumentsChange.keySet()) {
175       usages[j++] = info;
176     }
177
178     for (final TypeMigrationUsageInfo expr : myNewExpressionTypeChange.keySet()) {
179       usages[j++] = expr;
180     }
181     return sortMigratedUsages(usages);
182   }
183
184   private TypeMigrationUsageInfo[] sortMigratedUsages(TypeMigrationUsageInfo[] infos) {
185     final DFSTBuilder<TypeMigrationUsageInfo> builder = new DFSTBuilder<TypeMigrationUsageInfo>(GraphGenerator.create(
186       new GraphGenerator.SemiGraph<TypeMigrationUsageInfo>() {
187         @Override
188         public Collection<TypeMigrationUsageInfo> getNodes() {
189           final Set<TypeMigrationUsageInfo> infos = new HashSet<TypeMigrationUsageInfo>();
190           for (Map.Entry<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> entry : myRootsTree.entrySet()) {
191             infos.add(entry.getKey());
192             infos.addAll(ContainerUtil.map(entry.getValue(), new Function<Pair<TypeMigrationUsageInfo, PsiType>, TypeMigrationUsageInfo>() {
193               @Override
194               public TypeMigrationUsageInfo fun(Pair<TypeMigrationUsageInfo, PsiType> pair) {
195                 return pair.getFirst();
196               }
197             }));
198           }
199           return infos;
200         }
201
202         @Override
203         public Iterator<TypeMigrationUsageInfo> getIn(TypeMigrationUsageInfo n) {
204           final HashSet<Pair<TypeMigrationUsageInfo, PsiType>> rawNodes = myRootsTree.get(n);
205           if (rawNodes == null) {
206             return Collections.<TypeMigrationUsageInfo>emptyList().iterator();
207           }
208           final List<TypeMigrationUsageInfo> in =
209             ContainerUtil.map(rawNodes, new Function<Pair<TypeMigrationUsageInfo, PsiType>, TypeMigrationUsageInfo>() {
210               @Override
211               public TypeMigrationUsageInfo fun(Pair<TypeMigrationUsageInfo, PsiType> pair) {
212                 return pair.getFirst();
213               }
214             });
215           return in.iterator();
216         }
217       }));
218     final Comparator<TypeMigrationUsageInfo> cmp = builder.comparator();
219
220     Arrays.sort(infos, new Comparator<TypeMigrationUsageInfo>() {
221       @Override
222       public int compare(final TypeMigrationUsageInfo info1, final TypeMigrationUsageInfo info2) {
223         final TypeMigrationUsageInfo i1 = info1.getOwnerRoot();
224         final TypeMigrationUsageInfo i2 = info2.getOwnerRoot();
225         if (i1 == null && i2 == null) {
226           return 0;
227         }
228         if (i1 == null) {
229           return 1;
230         }
231         if (i2 == null) {
232           return -1;
233         }
234
235         final int res = cmp.compare(i1, i2);
236         if (res != 0) {
237           return res;
238         }
239         final PsiElement element1 = info1.getElement();
240         final PsiElement element2 = info2.getElement();
241         LOG.assertTrue(element1 != null && element2 != null);
242         return element2.getTextRange().getStartOffset() - element1.getTextRange().getStartOffset();
243       }
244     });
245
246     return infos;
247   }
248
249
250   public void change(final TypeMigrationUsageInfo usageInfo, @NotNull  Consumer<PsiNewExpression> consumer) {
251     final PsiElement element = usageInfo.getElement();
252     if (element == null) return;
253     final Project project = element.getProject();
254     if (element instanceof PsiExpression) {
255       final PsiExpression expression = (PsiExpression)element;
256       if (element instanceof PsiNewExpression) {
257         for (Map.Entry<TypeMigrationUsageInfo, PsiType> info : myNewExpressionTypeChange.entrySet()) {
258           final PsiElement expressionToReplace = info.getKey().getElement();
259           if (expression.equals(expressionToReplace)) {
260             final PsiNewExpression newExpression =
261               TypeMigrationReplacementUtil.replaceNewExpressionType(project, (PsiNewExpression)expressionToReplace, info);
262             if (newExpression != null) {
263               consumer.consume(newExpression);
264             }
265           }
266         }
267       }
268       final Object conversion = myConversions.get(element);
269       if (conversion != null) {
270         myConversions.remove(element);
271         TypeMigrationReplacementUtil.replaceExpression(expression, project, conversion);
272       }
273     } else if (element instanceof PsiReferenceParameterList) {
274       for (Map.Entry<TypeMigrationUsageInfo, PsiClassType> entry : myClassTypeArgumentsChange.entrySet()) {
275         if (element.equals(entry.getKey().getElement())) { //todo check null
276           final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
277           try {
278             element.getParent().replace(factory.createReferenceElementByType(entry.getValue()));
279           }
280           catch (IncorrectOperationException e) {
281             LOG.error(e);
282           }
283         }
284       }
285     }
286     else {
287       TypeMigrationReplacementUtil.migratePsiMemberType(element, project, getTypeEvaluator().getType(usageInfo));
288     }
289   }
290
291   void postProcessNewExpression(@NotNull PsiNewExpression expression) {
292     TypeMigrationReplacementUtil.tryToReplaceWithDiamond(expression, null);
293   }
294
295   @Nullable
296   Object getConversion(PsiElement element) {
297     return myConversions.get(element);
298   }
299
300   public TypeMigrationUsageInfo[] getMigratedUsages(boolean autoMigrate, final PsiElement... roots) {
301     if (myMigratedUsages == null) {
302       myShowWarning = autoMigrate;
303       migrate(autoMigrate, roots);
304       myMigratedUsages = getMigratedUsages();
305     }
306     return myMigratedUsages;
307   }
308
309   @Nullable
310   public Set<PsiElement> getTypeUsages(final TypeMigrationUsageInfo element, final TypeMigrationUsageInfo currentRoot) {
311     return myRootUsagesTree.get(Pair.create(element, currentRoot));
312   }
313
314   void convertExpression(final PsiExpression expr, final PsiType toType, final PsiType fromType, final boolean isCovariantPosition) {
315     final TypeConversionDescriptorBase conversion = myRules.findConversion(fromType, toType, expr instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)expr).resolveMethod() : null, expr,
316                                                      isCovariantPosition, this);
317
318     if (conversion == null) {
319       markFailedConversion(Pair.create(fromType, toType), expr);
320     }
321     else {
322       setConversionMapping(expr, conversion);
323     }
324   }
325
326   public void migrateExpressionType(final PsiExpression expr, final PsiType migrationType, final PsiElement place, boolean alreadyProcessed, final boolean isCovariant) {
327     PsiType originalType = expr.getType();
328
329     if (originalType == null || originalType.equals(migrationType)) return;
330
331     if (originalType.equals(PsiType.NULL)) {
332       if (migrationType instanceof PsiPrimitiveType) {
333         markFailedConversion(Pair.create(originalType, migrationType), expr);
334       }
335       return;
336     }
337
338     if (expr instanceof PsiConditionalExpression) {
339       final PsiConditionalExpression condExpr = (PsiConditionalExpression)expr;
340       for (PsiExpression e : ContainerUtil.newArrayList(condExpr.getThenExpression(), condExpr.getElseExpression())) {
341         if (e != null) {
342           migrateExpressionType(e, migrationType, place, alreadyProcessed, false);
343         }
344       }
345       getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType);
346       return;
347     } else if (expr instanceof PsiClassObjectAccessExpression) {
348       if (!TypeConversionUtil.isAssignable(migrationType, expr.getType())) {
349         markFailedConversion(Pair.create(expr.getType(), migrationType), expr);
350         return;
351       }
352     } else if (expr instanceof PsiArrayInitializerExpression && migrationType instanceof PsiArrayType) {
353       final PsiExpression[] initializers = ((PsiArrayInitializerExpression)expr).getInitializers();
354       for (PsiExpression initializer : initializers) {
355         migrateExpressionType(initializer, ((PsiArrayType)migrationType).getComponentType(), expr, alreadyProcessed, true);
356       }
357       getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType);
358       return;
359     } else if (expr instanceof PsiArrayAccessExpression) {
360       migrateExpressionType(((PsiArrayAccessExpression)expr).getArrayExpression(), migrationType.createArrayType(), place, alreadyProcessed, isCovariant);
361       return;
362     }
363     else if (expr instanceof PsiReferenceExpression) {
364       final PsiElement resolved = ((PsiReferenceExpression)expr).resolve();
365       if (resolved != null) {
366         if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) {
367           convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant);
368         }
369       }
370       return;
371     }
372     else if (expr instanceof PsiMethodCallExpression) {
373       final PsiMethod resolved = ((PsiMethodCallExpression)expr).resolveMethod();
374       if (resolved != null) {
375         if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) {
376           convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant);
377         }
378       }
379       return;
380     }
381     else if (expr instanceof PsiNewExpression) {
382       if (originalType.getArrayDimensions() == migrationType.getArrayDimensions()) {
383         if (migrationType.getArrayDimensions() > 0) {
384           final PsiType elemenType = ((PsiArrayType)migrationType).getComponentType();
385
386           final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)expr).getArrayInitializer();
387
388           if (arrayInitializer != null) {
389             final PsiExpression[] initializers = arrayInitializer.getInitializers();
390             for (int i = initializers.length - 1; i >= 0; i--) {
391               migrateExpressionType(initializers[i], elemenType, place, alreadyProcessed, true);
392             }
393           }
394
395           if (isGenericsArrayType(elemenType)){
396             markFailedConversion(Pair.create(originalType, migrationType), expr);
397             return;
398           }
399
400           final TypeMigrationUsageInfo usageInfo = new TypeMigrationUsageInfo(expr);
401           usageInfo.setOwnerRoot(myCurrentRoot);
402           myNewExpressionTypeChange.put(usageInfo, migrationType);
403           getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType);
404           return;
405         } else {
406           if (migrationType instanceof PsiClassType && originalType instanceof PsiClassType && ((PsiClassType)migrationType).rawType().isAssignableFrom(((PsiClassType)originalType).rawType())) {
407             final PsiClass originalClass = PsiUtil.resolveClassInType(originalType);
408             if (originalClass instanceof PsiAnonymousClass) {
409               originalType = ((PsiAnonymousClass)originalClass).getBaseClassType();
410             }
411             final PsiType type = TypeEvaluator.substituteType(migrationType, originalType, true, ((PsiClassType)originalType).resolveGenerics().getElement(),
412                                                               JavaPsiFacade.getElementFactory(expr.getProject()).createType(((PsiClassType)originalType).resolve(), PsiSubstitutor.EMPTY));
413             if (type != null){
414               final TypeMigrationUsageInfo usageInfo = new TypeMigrationUsageInfo(expr);
415               usageInfo.setOwnerRoot(myCurrentRoot);
416               myNewExpressionTypeChange.put(usageInfo, type);
417               getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), type);
418               return;
419             }
420           }
421         }
422       }
423
424     }
425     else if (expr instanceof PsiLambdaExpression) {
426       //TODO conversion of lambda expression now works incorrectly [Dmitry Batkovich]
427       return;
428     }
429
430     convertExpression(expr, migrationType, originalType, isCovariant);
431   }
432
433   private static boolean isGenericsArrayType(final PsiType elemenType) {
434     if (elemenType instanceof PsiClassType && ((PsiClassType)elemenType).hasParameters()) {
435       return true;
436     } else if (elemenType instanceof PsiArrayType) {
437       final PsiType componentType = ((PsiArrayType)elemenType).getComponentType();
438       return isGenericsArrayType(componentType);
439     }
440     return false;
441   }
442
443   boolean addMigrationRoot(PsiElement element, PsiType type, final PsiElement place, boolean alreadyProcessed, final boolean isContraVariantPosition) {
444     return addMigrationRoot(element, type, place, alreadyProcessed, isContraVariantPosition, false);
445   }
446
447   boolean addMigrationRoot(PsiElement element,
448                            PsiType type,
449                            final PsiElement place,
450                            boolean alreadyProcessed,
451                            final boolean isContraVariantPosition,
452                            final boolean userDefinedType) {
453     if (type.equals(PsiType.NULL)) {
454       return false;
455     }
456
457     final PsiElement resolved = Util.normalizeElement(element);
458
459     final SearchScope searchScope = myRules.getSearchScope();
460     if (!resolved.isPhysical() || !PsiSearchScopeUtil.isInScope(searchScope, resolved)) {
461       return false;
462     }
463
464     final PsiType originalType = getElementType(resolved);
465
466     LOG.assertTrue(originalType != null);
467
468     type = userDefinedType ? type : TypeEvaluator.substituteType(type, originalType, isContraVariantPosition);
469
470     if (!userDefinedType) {
471       if (typeContainsTypeParameters(originalType)) return false;
472     }
473
474     if (type instanceof PsiCapturedWildcardType) {
475       return false;
476     }
477
478     if (resolved instanceof PsiMethod) {
479       final PsiMethod method = ((PsiMethod)resolved);
480       final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY);
481       final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method);
482       final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length];
483       for (int i = -1; i < methods.length; i++) {
484         final TypeMigrationUsageInfo m;
485         if (i < 0) {
486           m = overridenUsageInfo;
487         }
488         else {
489           overriders[i] = new OverriderUsageInfo(methods[i], method);
490           m = overriders[i];
491         }
492
493         alreadyProcessed = addRoot(m, type, place, alreadyProcessed);
494       }
495       overridenUsageInfo.setOverriders(overriders);
496
497       return !alreadyProcessed;
498     }
499     else if (resolved instanceof PsiParameter && ((PsiParameter)resolved).getDeclarationScope() instanceof PsiMethod) {
500       final PsiMethod method = (PsiMethod)((PsiParameter)resolved).getDeclarationScope();
501
502       final int index = method.getParameterList().getParameterIndex(((PsiParameter)resolved));
503       final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY);
504
505       final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length];
506       final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method.getParameterList().getParameters()[index]);
507       for (int i = -1; i < methods.length; i++) {
508         final PsiMethod m = i < 0 ? method : methods[i];
509         final PsiParameter p = m.getParameterList().getParameters()[index];
510         final TypeMigrationUsageInfo paramUsageInfo;
511         if (i < 0) {
512           paramUsageInfo = overridenUsageInfo;
513         }
514         else {
515           overriders[i] = new OverriderUsageInfo(p, method);
516           paramUsageInfo = overriders[i];
517         }
518         alreadyProcessed = addRoot(paramUsageInfo, type, place, alreadyProcessed);
519       }
520
521       overridenUsageInfo.setOverriders(overriders);
522
523       return !alreadyProcessed;
524     }
525     else {
526       return !addRoot(new TypeMigrationUsageInfo(resolved), type, place, alreadyProcessed);
527     }
528   }
529
530   static boolean typeContainsTypeParameters(PsiType originalType) {
531     if (originalType instanceof PsiClassType) {
532       final PsiClassType psiClassType = (PsiClassType)originalType;
533       if (psiClassType.resolve() instanceof PsiTypeParameter) {
534         return true;
535       }
536       for (PsiType paramType : psiClassType.getParameters()) {
537         if (paramType instanceof PsiClassType && ((PsiClassType)paramType).resolve() instanceof PsiTypeParameter) return true;
538       }
539     }
540     return false;
541   }
542
543
544   @Nullable
545   public static PsiType getElementType(final PsiElement resolved) {
546     if (resolved instanceof PsiVariable) {
547       return ((PsiVariable)resolved).getType();
548     }
549     else {
550       if (resolved instanceof PsiMethod) {
551         return (((PsiMethod)resolved).getReturnType());
552       }
553       else if (resolved instanceof PsiExpression){
554         return (((PsiExpression)resolved).getType());
555       } else if (resolved instanceof PsiReferenceParameterList) {
556         PsiElement parent = resolved.getParent();
557         while (parent != null) {
558           LOG.assertTrue(parent instanceof PsiJavaCodeReferenceElement);
559           final PsiClass psiClass = (PsiClass)((PsiJavaCodeReferenceElement)parent).resolve();
560           final PsiClass containingClass = PsiTreeUtil.getParentOfType(parent, PsiClass.class);
561           if (psiClass != null && containingClass != null) {
562            final PsiSubstitutor classSubstitutor = TypeConversionUtil.getClassSubstitutor(psiClass, containingClass, PsiSubstitutor.EMPTY);
563            if (classSubstitutor != null) {
564              return JavaPsiFacade.getElementFactory(parent.getProject()).createType(psiClass, classSubstitutor);
565            }
566           }
567           parent = PsiTreeUtil.getParentOfType(parent, PsiJavaCodeReferenceElement.class, true);
568         }
569       } else if (resolved instanceof PsiClass) {
570         return JavaPsiFacade.getElementFactory(resolved.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY);
571       }
572     }
573     return null;
574   }
575
576   public void clearStopException() {
577     myException = null;
578   }
579   
580   boolean addRoot(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, boolean alreadyProcessed) {
581     if (myShowWarning && myMigrationRoots.size() > 10 && !ApplicationManager.getApplication().isUnitTestMode()) {
582       myShowWarning = false;
583       try {
584         final Runnable checkTimeToStopRunnable = new Runnable() {
585           public void run() {
586             if (Messages.showYesNoCancelDialog("Found more than 10 roots to migrate. Do you want to preview?", "Type Migration",
587                                                Messages.getWarningIcon()) == Messages.YES) {
588               myException = new MigrateException();
589             }
590           }
591         };
592         SwingUtilities.invokeLater(checkTimeToStopRunnable);
593       }
594       catch (Exception e) {
595         //do nothing
596       }
597     }
598     if (myException != null) throw myException;
599     rememberRootTrace(usageInfo, type, place, alreadyProcessed);
600     if (!alreadyProcessed && !getTypeEvaluator().setType(usageInfo, type)) {
601       alreadyProcessed = true;
602     }
603
604     if (!alreadyProcessed) myMigrationRoots.addFirst(Pair.create(usageInfo, type));
605     return alreadyProcessed;
606   }
607
608   private void rememberRootTrace(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, final boolean alreadyProcessed) {
609     if (myCurrentRoot != null) {
610       if (!alreadyProcessed) {
611         myProcessedRoots.add(usageInfo);
612       }
613
614       if (myProcessedRoots.contains(usageInfo)) {
615         HashSet<Pair<TypeMigrationUsageInfo, PsiType>> infos = myRootsTree.get(myCurrentRoot);
616         if (infos == null) {
617           infos = new HashSet<Pair<TypeMigrationUsageInfo, PsiType>>();
618           myRootsTree.put(myCurrentRoot, infos);
619         }
620         infos.add(Pair.create(usageInfo, type));
621       }
622       if (!(usageInfo instanceof OverriderUsageInfo)) { //hide the same usage for all overriders
623         setTypeUsage(usageInfo, place);
624       }
625     }
626   }
627
628   private void setTypeUsage(final TypeMigrationUsageInfo usageInfo, final PsiElement place) {
629     if (place != null) {
630       final Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo> rooted = Pair.create(usageInfo, myCurrentRoot);
631       Set<PsiElement> usages = myRootUsagesTree.get(rooted);
632       if (usages == null) {
633         usages = new HashSet<PsiElement>();
634         myRootUsagesTree.put(rooted, usages);
635       }
636       usages.add(place);
637     }
638   }
639   
640   public void setTypeUsage(final PsiElement element, final PsiElement place) {
641     setTypeUsage(new TypeMigrationUsageInfo(element), place);
642   }
643
644   void markFailedConversion(final Pair<PsiType, PsiType> typePair, final PsiExpression expression) {
645     LOG.assertTrue(typePair.getSecond() != null);
646     myFailedConversions.add(Pair.create(SmartPointerManager.getInstance(expression.getProject()).createSmartPsiElementPointer(expression), typePair.getSecond()));
647   }
648
649   void setConversionMapping(final PsiExpression expression, final Object obj) {
650     if (myConversions.get(expression) != null) {
651       return;
652     }
653
654     if (obj instanceof TypeConversionDescriptorBase) {
655       ((TypeConversionDescriptorBase)obj).setRoot(myCurrentRoot);
656     }
657     myConversions.put(expression, obj);
658   }
659
660   public PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType) {
661     return markRootUsages(element, migrationType, ReferencesSearch.search(element, myRules.getSearchScope(), false).toArray(new PsiReference[0]));
662   }
663
664   PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType, final PsiReference[] refs) {
665     final List<PsiReference> validReferences = new ArrayList<PsiReference>();
666     for (PsiReference ref1 : refs) {
667       final PsiElement ref = ref1.getElement();
668
669       if (ref != null) {
670         if (element instanceof PsiMethod) {
671           final PsiElement parent = Util.getEssentialParent(ref);
672
673           if (!(parent instanceof PsiMethodCallExpression)) {
674             continue;
675           }
676
677           getTypeEvaluator().setType(new TypeMigrationUsageInfo(parent), migrationType);
678         }
679         else if (element instanceof PsiVariable) {
680           if (ref instanceof PsiReferenceExpression) {
681             getTypeEvaluator().setType(new TypeMigrationUsageInfo(ref), PsiImplUtil.normalizeWildcardTypeByPosition(migrationType, (PsiReferenceExpression)ref));
682           }
683         }
684         else {
685           LOG.error("Method call expression or reference expression expected but found " + element.getClass().getName());
686           continue;
687         }
688         validReferences.add(ref1);
689       }
690     }
691
692     Collections.sort(validReferences, new Comparator<PsiReference>() {
693       public int compare(final PsiReference o1, final PsiReference o2) {
694         return o1.getElement().getTextOffset() - o2.getElement().getTextOffset();
695       }
696     });
697
698     return validReferences.toArray(new PsiReference[validReferences.size()]);
699   }
700
701   public void setRootAndMigrate(final TypeMigrationUsageInfo newRootUsageInfo, final PsiType migrationType, final PsiReference[] usages) {
702     final TypeMigrationUsageInfo oldRoot = getCurrentRoot();
703     setCurrentRoot(newRootUsageInfo);
704     PsiElement root = newRootUsageInfo.getElement();
705     if (root instanceof PsiMethod) {
706       migrateMethodReturnExpression(migrationType, (PsiMethod)root);
707     }
708     else if (root instanceof PsiParameter && ((PsiParameter)root).getDeclarationScope() instanceof PsiMethod) {
709       migrateMethodCallExpressions(migrationType, (PsiParameter)root, null);
710     }
711     else if (root instanceof PsiVariable || root instanceof PsiExpression) {
712       final PsiElement element = getContainingStatement(root);
713       element.accept(new TypeMigrationStatementProcessor(element, this));
714     }
715     else if (root instanceof PsiReferenceParameterList) {
716       final TypeMigrationUsageInfo info = new TypeMigrationUsageInfo(root);
717       info.setOwnerRoot(oldRoot);
718       myClassTypeArgumentsChange.put(info, (PsiClassType)migrationType);
719       new ClassTypeArgumentMigrationProcessor(this).migrateClassTypeParameter((PsiReferenceParameterList)root, migrationType);
720     }
721
722     final Set<PsiElement> processed = new HashSet<PsiElement>();
723     for (PsiReference usage : usages) {
724       migrateRootUsageExpression(usage, processed);
725     }
726   }
727
728   private static PsiElement getContainingStatement(final PsiElement root) {
729     final PsiStatement statement = PsiTreeUtil.getParentOfType(root, PsiStatement.class);
730     final PsiField field = PsiTreeUtil.getParentOfType(root, PsiField.class);
731     return statement != null ? statement : field != null ? field : root;
732   }
733
734   void migrateRootUsageExpression(final PsiReference usage, final Set<PsiElement> processed) {
735     final PsiElement ref = usage.getElement();
736     if (ref != null && ref.getLanguage() == JavaLanguage.INSTANCE) {
737       final PsiElement element = getContainingStatement(ref);
738       if (element != null && !processed.contains(element)) {
739         processed.add(element);
740         element.accept(new TypeMigrationStatementProcessor(ref, this));
741       }
742     }
743   }
744
745   void migrateMethodCallExpressions(final PsiType migrationType, final PsiParameter param, final PsiClass psiClass) {
746     boolean checkNumberOfArguments = false;
747     if (param.getType() instanceof PsiEllipsisType && !(migrationType instanceof PsiEllipsisType)) {
748       checkNumberOfArguments = true;
749     }
750     final PsiType strippedType =
751                   migrationType instanceof PsiEllipsisType ? ((PsiEllipsisType)migrationType).getComponentType() : migrationType;
752     final PsiMethod method = (PsiMethod)param.getDeclarationScope();
753     final PsiParameterList parameterList = method.getParameterList();
754     final int parametersCount = parameterList.getParametersCount();
755     final int index = parameterList.getParameterIndex(param);
756     final List<PsiReference> refs = filterReferences(psiClass, ReferencesSearch.search(method, method.getUseScope().intersectWith(myRules.getSearchScope()), false));
757     for (PsiReference ref1 : refs) {
758       final PsiElement ref = ref1.getElement();
759       final PsiElement parent = Util.getEssentialParent(ref);
760       if (parent instanceof PsiCallExpression) {
761         final PsiExpressionList argumentList = ((PsiCallExpression)parent).getArgumentList();
762         if (argumentList != null) {
763           final PsiExpression[] expressions = argumentList.getExpressions();
764           if (checkNumberOfArguments && parametersCount != expressions.length) {
765             markFailedConversion(Pair.create(param.getType(), migrationType), (PsiCallExpression)parent);
766           }
767           if (index > -1 && index < expressions.length) {
768             for (int idx = index; idx < (param.isVarArgs() ? expressions.length : index + 1); idx++) {
769               final PsiExpression actual = expressions[idx];
770               final PsiType type = getTypeEvaluator().evaluateType(actual);
771               if (type != null) {
772                 migrateExpressionType(actual, strippedType, parent, TypeConversionUtil.isAssignable(strippedType, type), true);
773               }
774             }
775           }
776         }
777       } else if (ref instanceof PsiDocTagValue) {
778         myConversions.put(ref, method);
779       }
780     }
781   }
782
783   private void migrateMethodReturnExpression(final PsiType migrationType, final PsiMethod method) {
784     final PsiCodeBlock block = method.getBody();
785     if (block != null) {
786       block.accept(new JavaRecursiveElementWalkingVisitor() {
787         @Override
788         public void visitReturnStatement(PsiReturnStatement statement) {
789           final PsiExpression value = statement.getReturnValue();
790           if (value != null) {
791             final PsiType type = getTypeEvaluator().evaluateType(value);
792             if (type != null && !type.equals(migrationType)) {
793               migrateExpressionType(value, migrationType, statement, TypeConversionUtil.isAssignable(migrationType, type), true);
794             }
795           }
796         }
797
798         @Override
799         public void visitClass(PsiClass aClass) {}
800
801         @Override
802         public void visitLambdaExpression(PsiLambdaExpression expression) {}
803       });
804     }
805   }
806
807   private void iterate() {
808     final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> roots =
809         (LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>)myMigrationRoots.clone();
810
811     myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>();
812
813     final PsiReference[][] cachedUsages = new PsiReference[roots.size()][];
814     int j = 0;
815
816     for (final Pair<TypeMigrationUsageInfo, PsiType> p : roots) {
817       cachedUsages[j++] = markRootUsages(p.getFirst().getElement(), p.getSecond());
818     }
819
820     j = 0;
821
822     for (final Pair<TypeMigrationUsageInfo, PsiType> root : roots) {
823       setRootAndMigrate(root.getFirst(), root.getSecond(), cachedUsages[j++]);
824     }
825   }
826
827   private void migrate(boolean autoMigrate, final PsiElement... victims) {
828     myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>();
829     myTypeEvaluator = new TypeEvaluator(myMigrationRoots, this);
830
831
832     final PsiType rootType = myRules.getMigrationRootType();
833     for (PsiElement victim : victims) {
834       addMigrationRoot(victim, rootType, null, false, true, true);
835     }
836
837     if (autoMigrate) {
838       while (myMigrationRoots.size() > 0) {
839         iterate();
840       }
841     }
842   }
843
844   public TypeEvaluator getTypeEvaluator() {
845     return myTypeEvaluator;
846   }
847
848   public Map<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> getRootsTree() {
849     return myRootsTree;
850   }
851
852   public void setCurrentRoot(final TypeMigrationUsageInfo currentRoot) {
853     myCurrentRoot = currentRoot;
854   }
855
856   TypeMigrationUsageInfo getCurrentRoot() {
857     return myCurrentRoot;
858   }
859
860   public LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> getMigrationRoots() {
861     return myMigrationRoots;
862   }
863
864   public static List<PsiReference> filterReferences(final PsiClass psiClass, final Query<PsiReference> memberReferences) {
865     final List<PsiReference> refs = new ArrayList<PsiReference>();
866     for (PsiReference memberReference : memberReferences) {
867       if (psiClass == null) {
868         refs.add(memberReference);
869       } else {
870         final PsiElement referencedElement = memberReference.getElement();
871         if (referencedElement instanceof PsiReferenceExpression) {
872           final PsiExpression qualifierExpression = ((PsiReferenceExpression)referencedElement).getQualifierExpression();
873           if (qualifierExpression != null) {
874             final PsiType qualifierType = qualifierExpression.getType();
875             if (qualifierType instanceof PsiClassType && psiClass == ((PsiClassType)qualifierType).resolve()) {
876               refs.add(memberReference);
877             }
878           } else {
879             if (psiClass == PsiTreeUtil.getParentOfType(referencedElement, PsiClass.class)) {
880               refs.add(memberReference);
881             }
882           }
883         }
884       }
885     }
886     return refs;
887   }
888
889   @TestOnly
890   public String getMigrationReport() {
891     final StringBuilder buffer = new StringBuilder();
892
893     buffer.append("Types:\n").append(getTypeEvaluator().getReport()).append("\n");
894
895     buffer.append("Conversions:\n");
896
897     final String[] conversions = new String[myConversions.size()];
898     int k = 0;
899
900     for (final PsiElement expr : myConversions.keySet()) {
901       final Object conversion = myConversions.get(expr);
902
903       if (conversion instanceof Pair && ((Pair)conversion).first == null) {
904         conversions[k++] = (expr.getText() + " -> " + ((Pair)conversion).second + "\n");
905       } else {
906         conversions[k++] = (expr.getText() + " -> " + conversion + "\n");
907       }
908     }
909
910     Arrays.sort(conversions, new Comparator<String>() {
911       public int compare(String x, String y) {
912         return x.compareTo(y);
913       }
914     });
915
916     for (String conversion : conversions) {
917       buffer.append(conversion);
918     }
919
920     buffer.append("\nNew expression type changes:\n");
921
922     final String[] newChanges = new String[myNewExpressionTypeChange.size()];
923     k = 0;
924
925     for (final Map.Entry<TypeMigrationUsageInfo, PsiType> entry : myNewExpressionTypeChange.entrySet()) {
926       final PsiElement element = entry.getKey().getElement();
927       newChanges[k++] = (element != null ? element.getText() : entry.getKey()) + " -> " + entry.getValue().getCanonicalText() + "\n";
928     }
929
930     Arrays.sort(newChanges, new Comparator<String>() {
931       public int compare(String x, String y) {
932         return x.compareTo(y);
933       }
934     });
935
936     for (String change : newChanges) {
937       buffer.append(change);
938     }
939
940     buffer.append("Fails:\n");
941
942     final ArrayList<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>>
943       failsList = new ArrayList<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>>(myFailedConversions);
944     Collections.sort(failsList, new Comparator<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>>() {
945       public int compare(final Pair<SmartPsiElementPointer<PsiExpression>, PsiType> o1, final Pair<SmartPsiElementPointer<PsiExpression>, PsiType> o2) {
946         final PsiElement element1 = o1.getFirst().getElement();
947         final PsiElement element2 = o2.getFirst().getElement();
948         if (element1 == null || element2 == null) return 0;
949         return element1.getText().compareTo(element2.getText());
950       }
951     });
952
953     for (final Pair<SmartPsiElementPointer<PsiExpression>, PsiType> p : failsList) {
954       final PsiElement element = p.getFirst().getElement();
955       if (element != null) {
956         buffer.append(element.getText()).append("->").append(p.getSecond().getCanonicalText()).append("\n");
957       }
958     }
959
960     return buffer.toString();
961   }
962
963   public static class MigrateException extends RuntimeException { }
964 }