EA-33078 - assert: TypeMigrationLabeler.migrateExpressionType
[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     if (originalType == null || originalType.equals(migrationType)) return;
253
254     if (originalType.equals(PsiType.NULL)) {
255       if (migrationType instanceof PsiPrimitiveType) {
256         markFailedConversion(new Pair<PsiType, PsiType>(originalType, migrationType), expr);
257       }
258       return;
259     }
260
261     if (expr instanceof PsiConditionalExpression) {
262
263     } else if (expr instanceof PsiClassObjectAccessExpression) {
264       if (!TypeConversionUtil.isAssignable(migrationType, expr.getType())) {
265         markFailedConversion(new Pair<PsiType, PsiType>(expr.getType(), migrationType), expr);
266         return;
267       }
268     } else if (expr instanceof PsiArrayInitializerExpression && migrationType instanceof PsiArrayType) {
269       final PsiExpression[] initializers = ((PsiArrayInitializerExpression)expr).getInitializers();
270       for (PsiExpression initializer : initializers) {
271         migrateExpressionType(initializer, ((PsiArrayType)migrationType).getComponentType(), expr, alreadyProcessed, true);
272       }
273       getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType);
274       return;
275     } else if (expr instanceof PsiArrayAccessExpression) {
276       migrateExpressionType(((PsiArrayAccessExpression)expr).getArrayExpression(), migrationType.createArrayType(), place, alreadyProcessed, isCovariant);
277       return;
278     }
279     else if (expr instanceof PsiReferenceExpression) {
280       final PsiElement resolved = ((PsiReferenceExpression)expr).resolve();
281       if (resolved != null) {
282         if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) {
283           convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant);
284         }
285       }
286       return;
287     }
288     else if (expr instanceof PsiMethodCallExpression) {
289       final PsiMethod resolved = ((PsiMethodCallExpression)expr).resolveMethod();
290       if (resolved != null) {
291         if (!addMigrationRoot(resolved, migrationType, place, alreadyProcessed, !isCovariant)) {
292           convertExpression(expr, migrationType, getTypeEvaluator().evaluateType(expr), isCovariant);
293         }
294       }
295       return;
296     }
297     else if (expr instanceof PsiNewExpression) {
298       if (originalType.getArrayDimensions() == migrationType.getArrayDimensions()) {
299         if (migrationType.getArrayDimensions() > 0) {
300           final PsiType elemenType = ((PsiArrayType)migrationType).getComponentType();
301
302           final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)expr).getArrayInitializer();
303
304           if (arrayInitializer != null) {
305             final PsiExpression[] initializers = arrayInitializer.getInitializers();
306             for (int i = initializers.length - 1; i >= 0; i--) {
307               migrateExpressionType(initializers[i], elemenType, place, alreadyProcessed, true);
308             }
309           }
310
311           if (isGenericsArrayType(elemenType)){
312             markFailedConversion(new Pair<PsiType, PsiType>(originalType, migrationType), expr);
313             return;
314           }
315
316           myNewExpressionTypeChange.put(new TypeMigrationUsageInfo(expr), migrationType);
317           getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), migrationType);
318           return;
319         } else {
320           if (migrationType instanceof PsiClassType && originalType instanceof PsiClassType && ((PsiClassType)migrationType).rawType().isAssignableFrom(((PsiClassType)originalType).rawType())) {
321             final PsiClass originalClass = PsiUtil.resolveClassInType(originalType);
322             if (originalClass instanceof PsiAnonymousClass) {
323               originalType = ((PsiAnonymousClass)originalClass).getBaseClassType();
324             }
325             final PsiType type = TypeEvaluator.substituteType(migrationType, originalType, true, ((PsiClassType)originalType).resolveGenerics().getElement(),
326                                                               JavaPsiFacade.getElementFactory(expr.getProject()).createType(((PsiClassType)originalType).resolve(), PsiSubstitutor.EMPTY));
327             if (type != null){
328               myNewExpressionTypeChange.put(new TypeMigrationUsageInfo(expr), type);
329               getTypeEvaluator().setType(new TypeMigrationUsageInfo(expr), type);
330               return;
331             }
332           }
333         }
334       }
335
336     }
337     
338     convertExpression(expr, migrationType, originalType, isCovariant);
339   }
340
341   private static boolean isGenericsArrayType(final PsiType elemenType) {
342     if (elemenType instanceof PsiClassType && ((PsiClassType)elemenType).hasParameters()) {
343       return true;
344     } else if (elemenType instanceof PsiArrayType) {
345       final PsiType componentType = ((PsiArrayType)elemenType).getComponentType();
346       return isGenericsArrayType(componentType);
347     }
348     return false;
349   }
350
351   boolean addMigrationRoot(PsiElement element, PsiType type, final PsiElement place, boolean alreadyProcessed, final boolean isContraVariantPosition) {
352     return addMigrationRoot(element, type, place, alreadyProcessed, isContraVariantPosition, false);
353   }
354
355   boolean addMigrationRoot(PsiElement element,
356                            PsiType type,
357                            final PsiElement place,
358                            boolean alreadyProcessed,
359                            final boolean isContraVariantPosition,
360                            final boolean userDefinedType) {
361     if (type.equals(PsiType.NULL)) {
362       return false;
363     }
364
365     final PsiElement resolved = Util.normalizeElement(element);
366
367     final SearchScope searchScope = myRules.getSearchScope();
368     if (!resolved.isPhysical() || !PsiSearchScopeUtil.isInScope(searchScope, resolved)) {
369       return false;
370     }
371
372     final PsiType originalType = getElementType(resolved);
373
374     LOG.assertTrue(originalType != null);
375
376     type = userDefinedType ? type : TypeEvaluator.substituteType(type, originalType, isContraVariantPosition);
377
378     if (!userDefinedType) {
379       if (typeContainsTypeParameters(originalType)) return false;
380     }
381
382     if (type instanceof PsiCapturedWildcardType) {
383       return false;
384     }
385
386     if (resolved instanceof PsiMethod) {
387       final PsiMethod method = ((PsiMethod)resolved);
388       final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY);
389       final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method);
390       final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length];
391       for (int i = -1; i < methods.length; i++) {
392         final TypeMigrationUsageInfo m;
393         if (i < 0) {
394           m = overridenUsageInfo;
395         }
396         else {
397           overriders[i] = new OverriderUsageInfo(methods[i], method);
398           m = overriders[i];
399         }
400
401         alreadyProcessed = addRoot(m, type, place, alreadyProcessed);
402       }
403       overridenUsageInfo.setOverriders(overriders);
404
405       return !alreadyProcessed;
406     }
407     else if (resolved instanceof PsiParameter && ((PsiParameter)resolved).getDeclarationScope() instanceof PsiMethod) {
408       final PsiMethod method = (PsiMethod)((PsiParameter)resolved).getDeclarationScope();
409
410       final int index = method.getParameterList().getParameterIndex(((PsiParameter)resolved));
411       final PsiMethod[] methods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY);
412
413       final OverriderUsageInfo[] overriders = new OverriderUsageInfo[methods.length];
414       final OverridenUsageInfo overridenUsageInfo = new OverridenUsageInfo(method.getParameterList().getParameters()[index]);
415       for (int i = -1; i < methods.length; i++) {
416         final PsiMethod m = i < 0 ? method : methods[i];
417         final PsiParameter p = m.getParameterList().getParameters()[index];
418         final TypeMigrationUsageInfo paramUsageInfo;
419         if (i < 0) {
420           paramUsageInfo = overridenUsageInfo;
421         }
422         else {
423           overriders[i] = new OverriderUsageInfo(p, method);
424           paramUsageInfo = overriders[i];
425         }
426         alreadyProcessed = addRoot(paramUsageInfo, type, place, alreadyProcessed);
427       }
428
429       overridenUsageInfo.setOverriders(overriders);
430
431       return !alreadyProcessed;
432     }
433     else {
434       return !addRoot(new TypeMigrationUsageInfo(resolved), type, place, alreadyProcessed);
435     }
436   }
437
438   static boolean typeContainsTypeParameters(PsiType originalType) {
439     if (originalType instanceof PsiClassType) {
440       final PsiClassType psiClassType = (PsiClassType)originalType;
441       if (psiClassType.resolve() instanceof PsiTypeParameter) {
442         return true;
443       }
444       for (PsiType paramType : psiClassType.getParameters()) {
445         if (paramType instanceof PsiClassType && ((PsiClassType)paramType).resolve() instanceof PsiTypeParameter) return true;
446       }
447     }
448     return false;
449   }
450
451
452   @Nullable
453   public static PsiType getElementType(final PsiElement resolved) {
454     if (resolved instanceof PsiVariable) {
455       return ((PsiVariable)resolved).getType();
456     }
457     else {
458       if (resolved instanceof PsiMethod) {
459         return (((PsiMethod)resolved).getReturnType());
460       }
461       else if (resolved instanceof PsiExpression){
462         return (((PsiExpression)resolved).getType());
463       } else if (resolved instanceof PsiReferenceParameterList) {
464         final PsiElement parent = resolved.getParent();
465         LOG.assertTrue(parent instanceof PsiJavaCodeReferenceElement);
466         final PsiClass psiClass = (PsiClass)((PsiJavaCodeReferenceElement)parent).resolve();
467         return JavaPsiFacade.getElementFactory(parent.getProject()).createType(psiClass, TypeConversionUtil.getSuperClassSubstitutor(psiClass, PsiTreeUtil.getParentOfType(parent,
468                                                                                                                                      PsiClass.class),
469                                                                                                          PsiSubstitutor.EMPTY));
470       } else if (resolved instanceof PsiClass) {
471         return JavaPsiFacade.getElementFactory(resolved.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY);
472       }
473     }
474     LOG.error("should not happen: " + resolved.getClass());
475     return null;
476   }
477
478   boolean addRoot(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, boolean alreadyProcessed) {
479     if (myShowWarning && myMigrationRoots.size() > 10 && !ApplicationManager.getApplication().isUnitTestMode()) {
480       myShowWarning = false;
481       try {
482         final Runnable checkTimeToStopRunnable = new Runnable() {
483           public void run() {
484             if (Messages.showYesNoCancelDialog("Found more than 10 roots to migrate. Do you want to preview?", "Type Migration",
485                                                Messages.getWarningIcon()) == DialogWrapper.OK_EXIT_CODE) {
486               myException = new MigrateException();
487             }
488           }
489         };
490         SwingUtilities.invokeLater(checkTimeToStopRunnable);
491       }
492       catch (Exception e) {
493         //do nothing
494       }
495     }
496     if (myException != null) throw myException;
497     rememberRootTrace(usageInfo, type, place, alreadyProcessed);
498     if (!alreadyProcessed && !getTypeEvaluator().setType(usageInfo, type)) {
499       alreadyProcessed = true;
500     }
501
502     if (!alreadyProcessed) myMigrationRoots.addFirst(new Pair<TypeMigrationUsageInfo, PsiType>(usageInfo, type));
503     return alreadyProcessed;
504   }
505
506   private void rememberRootTrace(final TypeMigrationUsageInfo usageInfo, final PsiType type, final PsiElement place, final boolean alreadyProcessed) {
507     if (myCurrentRoot != null) {
508       if (!alreadyProcessed) {
509         myProcessedRoots.add(usageInfo);
510       }
511
512       if (myProcessedRoots.contains(usageInfo)) {
513         HashSet<Pair<TypeMigrationUsageInfo, PsiType>> infos = myRootsTree.get(myCurrentRoot);
514         if (infos == null) {
515           infos = new HashSet<Pair<TypeMigrationUsageInfo, PsiType>>();
516           myRootsTree.put(myCurrentRoot, infos);
517         }
518         infos.add(Pair.create(usageInfo, type));
519       }
520       if (!(usageInfo instanceof OverriderUsageInfo)) { //hide the same usage for all overriders
521         setTypeUsage(usageInfo, place);
522       }
523     }
524   }
525
526   private void setTypeUsage(final TypeMigrationUsageInfo usageInfo, final PsiElement place) {
527     if (place != null) {
528       final Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo> rooted = Pair.create(usageInfo, myCurrentRoot);
529       Set<PsiElement> usages = myRootUsagesTree.get(rooted);
530       if (usages == null) {
531         usages = new HashSet<PsiElement>();
532         myRootUsagesTree.put(rooted, usages);
533       }
534       usages.add(place);
535     }
536   }
537   
538   public void setTypeUsage(final PsiElement element, final PsiElement place) {
539     setTypeUsage(new TypeMigrationUsageInfo(element), place);
540   }
541
542   void markFailedConversion(final Pair<PsiType, PsiType> typePair, final PsiExpression expression) {
543     LOG.assertTrue(expression.getType() != null);
544     LOG.assertTrue(typePair.getSecond() != null);
545     myFailedConversions.add(new Pair<PsiAnchor, PsiType>(PsiAnchor.create(expression), typePair.getSecond()));
546   }
547
548   void setConversionMapping(final PsiExpression expression, final Object obj) {
549     if (myConversions.get(expression) != null) {
550       return;
551     }
552
553     if (obj instanceof TypeConversionDescriptorBase) {
554       ((TypeConversionDescriptorBase)obj).setRoot(myCurrentRoot);
555     }
556     myConversions.put(expression, obj);
557   }
558
559   public PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType) {
560     return markRootUsages(element, migrationType, ReferencesSearch.search(element, myRules.getSearchScope(), false).toArray(new PsiReference[0]));
561   }
562
563   PsiReference[] markRootUsages(final PsiElement element, final PsiType migrationType, final PsiReference[] refs) {
564     final List<PsiReference> validReferences = new ArrayList<PsiReference>();
565     for (PsiReference ref1 : refs) {
566       final PsiElement ref = ref1.getElement();
567
568       if (ref != null) {
569         if (element instanceof PsiMethod) {
570           final PsiElement parent = Util.getEssentialParent(ref);
571
572           if (!(parent instanceof PsiMethodCallExpression)) {
573             continue;
574           }
575
576           getTypeEvaluator().setType(new TypeMigrationUsageInfo(parent), migrationType);
577         }
578         else if (element instanceof PsiVariable) {
579           if (ref instanceof PsiReferenceExpression) {
580             getTypeEvaluator().setType(new TypeMigrationUsageInfo(ref), PsiImplUtil.normalizeWildcardTypeByPosition(migrationType, (PsiReferenceExpression)ref));
581           }
582         }
583         else {
584           LOG.error("Method call expression or reference expression expected but found " + element.getClass().getName());
585           continue;
586         }
587         validReferences.add(ref1);
588       }
589     }
590
591     Collections.sort(validReferences, new Comparator<PsiReference>() {
592       public int compare(final PsiReference o1, final PsiReference o2) {
593         return o1.getElement().getTextOffset() - o2.getElement().getTextOffset();
594       }
595     });
596
597     return validReferences.toArray(new PsiReference[validReferences.size()]);
598   }
599
600   public void migrateRoot(final PsiElement root, final PsiType migrationType, final PsiReference[] usages) {
601     if (root instanceof PsiMethod) {
602       migrateMethodReturnExpression(migrationType, (PsiMethod)root);
603     }
604     else if (root instanceof PsiParameter && ((PsiParameter)root).getDeclarationScope() instanceof PsiMethod) {
605       migrateMethodCallExpressions(migrationType, (PsiParameter)root, null);
606     }
607     else if (root instanceof PsiVariable || root instanceof PsiExpression){
608       final PsiElement element = getContainingStatement(root);
609       element.accept(new TypeMigrationStatementProcessor(element, this));
610     } else if (root instanceof PsiReferenceParameterList) {
611       myClassTypeArgumentsChange.put(new TypeMigrationUsageInfo(root), (PsiClassType)migrationType);
612       new ClassTypeArgumentMigrationProcessor(this).migrateClassTypeParameter((PsiReferenceParameterList)root, migrationType);
613     }
614
615     final Set<PsiElement> processed = new HashSet<PsiElement>();
616     for (PsiReference usage : usages) {
617       migrateRootUsageExpression(usage, processed);
618     }
619   }
620
621   private static PsiElement getContainingStatement(final PsiElement root) {
622     final PsiStatement statement = PsiTreeUtil.getParentOfType(root, PsiStatement.class);
623     final PsiField field = PsiTreeUtil.getParentOfType(root, PsiField.class);
624     return statement != null ? statement : field != null ? field : root;
625   }
626
627   void migrateRootUsageExpression(final PsiReference usage, final Set<PsiElement> processed) {
628     final PsiElement ref = usage.getElement();
629     if (ref != null && ref.getLanguage() == JavaLanguage.INSTANCE) {
630       final PsiElement element = getContainingStatement(ref);
631       if (element != null && !processed.contains(element)) {
632         processed.add(element);
633         element.accept(new TypeMigrationStatementProcessor(ref, this));
634       }
635     }
636   }
637
638   void migrateMethodCallExpressions(final PsiType migrationType, final PsiParameter param, final PsiClass psiClass) {
639     boolean checkNumberOfArguments = false;
640     if (param.getType() instanceof PsiEllipsisType && !(migrationType instanceof PsiEllipsisType)) {
641       checkNumberOfArguments = true;
642     }
643     final PsiType strippedType =
644                   migrationType instanceof PsiEllipsisType ? ((PsiEllipsisType)migrationType).getComponentType() : migrationType;
645     final PsiMethod method = (PsiMethod)param.getDeclarationScope();
646     final PsiParameterList parameterList = method.getParameterList();
647     final int parametersCount = parameterList.getParametersCount();
648     final int index = parameterList.getParameterIndex(param);
649     final List<PsiReference> refs = filterReferences(psiClass, ReferencesSearch.search(method, method.getUseScope().intersectWith(myRules.getSearchScope()), false));
650     for (PsiReference ref1 : refs) {
651       final PsiElement ref = ref1.getElement();
652       final PsiElement parent = Util.getEssentialParent(ref);
653       if (parent instanceof PsiCallExpression) {
654         final PsiExpressionList argumentList = ((PsiCallExpression)parent).getArgumentList();
655         if (argumentList != null) {
656           final PsiExpression[] expressions = argumentList.getExpressions();
657           if (checkNumberOfArguments && parametersCount != expressions.length) {
658             markFailedConversion(new Pair<PsiType, PsiType>(param.getType(), migrationType), (PsiCallExpression)parent);
659           }
660           if (index > -1 && index < expressions.length) {
661             for (int idx = index; idx < (param.isVarArgs() ? expressions.length : index + 1); idx++) {
662               final PsiExpression actual = expressions[idx];
663               final PsiType type = getTypeEvaluator().evaluateType(actual);
664               if (type != null) {
665                 migrateExpressionType(actual, strippedType, parent, TypeConversionUtil.isAssignable(strippedType, type), true);
666               }
667             }
668           }
669         }
670       } else if (ref instanceof PsiDocTagValue) {
671         myConversions.put(ref, method);
672       }
673     }
674   }
675
676   private void migrateMethodReturnExpression(final PsiType migrationType, final PsiMethod method) {
677     final PsiCodeBlock block = method.getBody();
678     if (block != null) {
679       block.accept(new JavaRecursiveElementWalkingVisitor() {
680         @Override
681         public void visitReturnStatement(PsiReturnStatement statement) {
682           final PsiExpression value = statement.getReturnValue();
683           if (value != null) {
684             final PsiType type = getTypeEvaluator().evaluateType(value);
685             if (type != null && !type.equals(migrationType)) {
686               migrateExpressionType(value, migrationType, statement, TypeConversionUtil.isAssignable(migrationType, type), true);
687             }
688           }
689         }
690       });
691     }
692   }
693
694   private void iterate() {
695     final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> roots =
696         (LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>)myMigrationRoots.clone();
697
698     myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>();
699
700     final PsiReference[][] cachedUsages = new PsiReference[roots.size()][];
701     int j = 0;
702
703     for (final Pair<TypeMigrationUsageInfo, PsiType> p : roots) {
704       cachedUsages[j++] = markRootUsages(p.getFirst().getElement(), p.getSecond());
705     }
706
707     j = 0;
708
709     for (final Pair<TypeMigrationUsageInfo, PsiType> root : roots) {
710       myCurrentRoot = root.getFirst();
711       migrateRoot(root.getFirst().getElement(), root.getSecond(), cachedUsages[j++]);
712     }
713   }
714
715   private void migrate(boolean autoMigrate, final PsiElement... victims) {
716     myMigrationRoots = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>();
717     myTypeEvaluator = new TypeEvaluator(myMigrationRoots, this);
718
719
720     final PsiType rootType = myRules.getMigrationRootType();
721     for (PsiElement victim : victims) {
722       addMigrationRoot(victim, rootType, null, false, true, true);
723     }
724
725     if (autoMigrate) {
726       while (myMigrationRoots.size() > 0) {
727         iterate();
728       }
729     }
730   }
731
732   public TypeEvaluator getTypeEvaluator() {
733     return myTypeEvaluator;
734   }
735
736   public Map<TypeMigrationUsageInfo, HashSet<Pair<TypeMigrationUsageInfo, PsiType>>> getRootsTree() {
737     return myRootsTree;
738   }
739
740   public void setCurrentRoot(final TypeMigrationUsageInfo currentRoot) {
741     myCurrentRoot = currentRoot;
742   }
743
744   public LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> getMigrationRoots() {
745     return myMigrationRoots;
746   }
747
748   public static List<PsiReference> filterReferences(final PsiClass psiClass, final Query<PsiReference> memberReferences) {
749     final List<PsiReference> refs = new ArrayList<PsiReference>();
750     for (PsiReference memberReference : memberReferences) {
751       if (psiClass == null) {
752         refs.add(memberReference);
753       } else {
754         final PsiElement referencedElement = memberReference.getElement();
755         if (referencedElement instanceof PsiReferenceExpression) {
756           final PsiExpression qualifierExpression = ((PsiReferenceExpression)referencedElement).getQualifierExpression();
757           if (qualifierExpression != null) {
758             final PsiType qualifierType = qualifierExpression.getType();
759             if (qualifierType instanceof PsiClassType && psiClass == ((PsiClassType)qualifierType).resolve()) {
760               refs.add(memberReference);
761             }
762           } else {
763             if (psiClass == PsiTreeUtil.getParentOfType(referencedElement, PsiClass.class)) {
764               refs.add(memberReference);
765             }
766           }
767         }
768       }
769     }
770     return refs;
771   }
772
773   @TestOnly
774   public String getMigrationReport() {
775     final StringBuilder buffer = new StringBuilder();
776
777     buffer.append("Types:\n").append(getTypeEvaluator().getReport()).append("\n");
778
779     buffer.append("Conversions:\n");
780
781     final String[] conversions = new String[myConversions.size()];
782     int k = 0;
783
784     for (final PsiElement expr : myConversions.keySet()) {
785       final Object conversion = myConversions.get(expr);
786
787       if (conversion instanceof Pair && ((Pair)conversion).first == null) {
788         conversions[k++] = (expr.getText() + " -> " + ((Pair)conversion).second + "\n");
789       } else {
790         conversions[k++] = (expr.getText() + " -> " + conversion + "\n");
791       }
792     }
793
794     Arrays.sort(conversions, new Comparator<String>() {
795       public int compare(String x, String y) {
796         return x.compareTo(y);
797       }
798     });
799
800     for (String conversion : conversions) {
801       buffer.append(conversion);
802     }
803
804     buffer.append("\nNew expression type changes:\n");
805
806     final String[] newChanges = new String[myNewExpressionTypeChange.size()];
807     k = 0;
808
809     for (final Map.Entry<TypeMigrationUsageInfo, PsiType> entry : myNewExpressionTypeChange.entrySet()) {
810       final PsiElement element = entry.getKey().getElement();
811       newChanges[k++] = (element != null ? element.getText() : entry.getKey()) + " -> " + entry.getValue().getCanonicalText() + "\n";
812     }
813
814     Arrays.sort(newChanges, new Comparator<String>() {
815       public int compare(String x, String y) {
816         return x.compareTo(y);
817       }
818     });
819
820     for (String change : newChanges) {
821       buffer.append(change);
822     }
823
824     buffer.append("Fails:\n");
825
826     final ArrayList<Pair<PsiAnchor, PsiType>> failsList = new ArrayList<Pair<PsiAnchor, PsiType>>(myFailedConversions);
827     Collections.sort(failsList, new Comparator<Pair<PsiAnchor, PsiType>>() {
828       public int compare(final Pair<PsiAnchor, PsiType> o1, final Pair<PsiAnchor, PsiType> o2) {
829         final PsiElement element1 = o1.getFirst().retrieve();
830         final PsiElement element2 = o2.getFirst().retrieve();
831         if (element1 == null || element2 == null) return 0;
832         return element1.getText().compareTo(element2.getText());
833       }
834     });
835
836     for (final Pair<PsiAnchor, PsiType> p : failsList) {
837       final PsiElement element = p.getFirst().retrieve();
838       if (element != null) {
839         buffer.append(element.getText()).append("->").append(p.getSecond().getCanonicalText()).append("\n");
840       }
841     }
842
843     return buffer.toString();
844   }
845
846   public static class MigrateException extends RuntimeException { }
847 }