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