[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / util / RefactoringUtil.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.refactoring.util;
3
4 import com.intellij.codeInsight.ExpectedTypeInfo;
5 import com.intellij.codeInsight.ExpectedTypesProvider;
6 import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
7 import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
8 import com.intellij.codeInsight.highlighting.HighlightManager;
9 import com.intellij.lang.java.JavaLanguage;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.editor.Editor;
12 import com.intellij.openapi.editor.RangeMarker;
13 import com.intellij.openapi.editor.colors.EditorColors;
14 import com.intellij.openapi.editor.colors.EditorColorsManager;
15 import com.intellij.openapi.editor.markup.RangeHighlighter;
16 import com.intellij.openapi.editor.markup.TextAttributes;
17 import com.intellij.openapi.project.Project;
18 import com.intellij.openapi.roots.ProjectRootManager;
19 import com.intellij.openapi.util.*;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vfs.VfsUtil;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.psi.*;
24 import com.intellij.psi.codeStyle.CodeStyleManager;
25 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
26 import com.intellij.psi.codeStyle.VariableKind;
27 import com.intellij.psi.impl.PsiImplUtil;
28 import com.intellij.psi.javadoc.PsiDocComment;
29 import com.intellij.psi.javadoc.PsiDocTag;
30 import com.intellij.psi.javadoc.PsiDocTagValue;
31 import com.intellij.psi.search.LocalSearchScope;
32 import com.intellij.psi.search.SearchScope;
33 import com.intellij.psi.search.searches.ReferencesSearch;
34 import com.intellij.psi.util.*;
35 import com.intellij.refactoring.PackageWrapper;
36 import com.intellij.refactoring.RefactoringBundle;
37 import com.intellij.refactoring.introduceField.ElementToWorkOn;
38 import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.ObjectUtils;
41 import com.intellij.util.text.UniqueNameGenerator;
42 import gnu.trove.THashMap;
43 import org.jetbrains.annotations.Contract;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import java.util.*;
48
49 public class RefactoringUtil {
50   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.util.RefactoringUtil");
51   public static final int EXPR_COPY_SAFE = 0;
52   public static final int EXPR_COPY_UNSAFE = 1;
53   public static final int EXPR_COPY_PROHIBITED = 2;
54
55   private RefactoringUtil() {
56   }
57
58   public static boolean isSourceRoot(final PsiDirectory directory) {
59     if (directory.getManager() == null) return false;
60     final Project project = directory.getProject();
61     final VirtualFile virtualFile = directory.getVirtualFile();
62     final VirtualFile sourceRootForFile = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(virtualFile);
63     return Comparing.equal(virtualFile, sourceRootForFile);
64   }
65
66   public static boolean isInStaticContext(PsiElement element, @Nullable final PsiClass aClass) {
67     return PsiUtil.getEnclosingStaticElement(element, aClass) != null;
68   }
69
70   @Deprecated
71   public static boolean isResolvableType(PsiType type) {
72     return !PsiTypesUtil.hasUnresolvedComponents(type);
73   }
74
75   public static PsiElement replaceOccurenceWithFieldRef(PsiExpression occurrence, PsiField newField, PsiClass destinationClass)
76     throws IncorrectOperationException {
77     final PsiManager manager = destinationClass.getManager();
78     final String fieldName = newField.getName();
79     final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
80     final PsiElement element = occurrence.getUserData(ElementToWorkOn.PARENT);
81     final PsiVariable psiVariable = facade.getResolveHelper().resolveAccessibleReferencedVariable(fieldName, element != null ? element : occurrence);
82     final PsiElementFactory factory = facade.getElementFactory();
83     if (psiVariable != null && psiVariable.equals(newField)) {
84       return IntroduceVariableBase.replace(occurrence, factory.createExpressionFromText(fieldName, null), manager.getProject());
85     }
86     else {
87       final PsiReferenceExpression ref = (PsiReferenceExpression)factory.createExpressionFromText("this." + fieldName, null);
88       if (!occurrence.isValid()) return null;
89       if (newField.hasModifierProperty(PsiModifier.STATIC)) {
90         ref.setQualifierExpression(factory.createReferenceExpression(destinationClass));
91       }
92       return IntroduceVariableBase.replace(occurrence, ref, manager.getProject());
93     }
94   }
95
96   /**
97    * @see JavaCodeStyleManager#suggestUniqueVariableName(String, PsiElement, boolean)
98    * Cannot use method from code style manager: a collision with fieldToReplace is not a collision
99    */
100   public static String suggestUniqueVariableName(String baseName, PsiElement place, PsiField fieldToReplace) {
101     for(int index = 0;;index++) {
102       final String name = index > 0 ? baseName + index : baseName;
103       final PsiManager manager = place.getManager();
104       PsiResolveHelper helper = JavaPsiFacade.getInstance(manager.getProject()).getResolveHelper();
105       PsiVariable refVar = helper.resolveAccessibleReferencedVariable(name, place);
106       if (refVar != null && !manager.areElementsEquivalent(refVar, fieldToReplace)) continue;
107       final boolean[] found = {false};
108       place.accept(new JavaRecursiveElementWalkingVisitor() {
109         @Override
110         public void visitClass(PsiClass aClass) {
111
112         }
113
114         @Override
115         public void visitVariable(PsiVariable variable) {
116           if (name.equals(variable.getName())) {
117             found[0] = true;
118             stopWalking();
119           }
120         }
121       });
122       if (found[0]) {
123         continue;
124       }
125
126       return name;
127     }
128   }
129
130   @Nullable
131   public static String suggestNewOverriderName(String oldOverriderName, String oldBaseName, String newBaseName) {
132     if (oldOverriderName.equals(oldBaseName)) {
133       return newBaseName;
134     }
135     int i;
136     if (oldOverriderName.startsWith(oldBaseName)) {
137       i = 0;
138     }
139     else {
140       i = StringUtil.indexOfIgnoreCase(oldOverriderName, oldBaseName, 0);
141     }
142     if (i >= 0) {
143       String newOverriderName = oldOverriderName.substring(0, i);
144       if (Character.isUpperCase(oldOverriderName.charAt(i))) {
145         newOverriderName += StringUtil.capitalize(newBaseName);
146       }
147       else {
148         newOverriderName += newBaseName;
149       }
150       final int j = i + oldBaseName.length();
151       if (j < oldOverriderName.length()) {
152         newOverriderName += oldOverriderName.substring(j);
153       }
154
155       return newOverriderName;
156     }
157     return null;
158   }
159
160   public static boolean hasOnDemandStaticImport(final PsiElement element, final PsiClass aClass) {
161     if (element.getContainingFile() instanceof PsiJavaFile) {
162       final PsiImportList importList = ((PsiJavaFile)element.getContainingFile()).getImportList();
163       if (importList != null) {
164         final PsiImportStaticStatement[] importStaticStatements = importList.getImportStaticStatements();
165         return Arrays.stream(importStaticStatements).anyMatch(stmt -> stmt.isOnDemand() && stmt.resolveTargetClass() == aClass);
166       }
167     }
168     return false;
169   }
170
171   public static PsiElement replaceElementsWithMap(PsiElement replaceIn, final Map<PsiElement, PsiElement> elementsToReplace) throws IncorrectOperationException {
172     for(Map.Entry<PsiElement, PsiElement> e: elementsToReplace.entrySet()) {
173       if (e.getKey() == replaceIn) {
174         return e.getKey().replace(e.getValue());
175       }
176       e.getKey().replace(e.getValue());
177     }
178     return replaceIn;
179   }
180
181   public static PsiElement getVariableScope(PsiLocalVariable localVar) {
182     if (!(localVar instanceof ImplicitVariable)) {
183       return localVar.getParent().getParent();
184     }
185     else {
186       return ((ImplicitVariable)localVar).getDeclarationScope();
187     }
188   }
189
190
191   @Nullable
192   public static PsiElement getParentStatement(@Nullable PsiElement place, boolean skipScopingStatements) {
193     PsiElement parent = place;
194     while (true) {
195       if (parent == null) return null;
196       if (parent instanceof PsiStatement) break;
197       if (parent instanceof PsiExpression && parent.getParent() instanceof PsiLambdaExpression) return parent;
198       parent = parent.getParent();
199     }
200     PsiElement parentStatement = parent;
201     while (parent instanceof PsiStatement && !(parent instanceof PsiSwitchLabeledRuleStatement)) {
202       if (!skipScopingStatements && ((parent instanceof PsiForStatement && parentStatement == ((PsiForStatement)parent).getBody()) || (
203         parent instanceof PsiForeachStatement && parentStatement == ((PsiForeachStatement)parent).getBody()) || (
204         parent instanceof PsiWhileStatement && parentStatement == ((PsiWhileStatement)parent).getBody()) || (
205         parent instanceof PsiIfStatement &&
206         (parentStatement == ((PsiIfStatement)parent).getThenBranch() || parentStatement == ((PsiIfStatement)parent).getElseBranch())))) {
207         return parentStatement;
208       }
209       parentStatement = parent;
210       parent = parent.getParent();
211     }
212     return parentStatement;
213   }
214
215
216   public static PsiElement getParentExpressionAnchorElement(PsiElement place) {
217     PsiElement parent = place.getUserData(ElementToWorkOn.PARENT);
218     if (place.getUserData(ElementToWorkOn.OUT_OF_CODE_BLOCK) != null) return parent;
219     if (parent == null) parent = place;
220     while (true) {
221       if (isExpressionAnchorElement(parent)) return parent;
222       if (parent instanceof PsiExpression && parent.getParent() instanceof PsiLambdaExpression) return parent;
223       parent = parent.getParent();
224       if (parent == null) return null;
225     }
226   }
227
228
229   public static boolean isExpressionAnchorElement(PsiElement element) {
230     if (element instanceof PsiDeclarationStatement && element.getParent() instanceof PsiForStatement) return false;
231     return element instanceof PsiStatement || element instanceof PsiClassInitializer || element instanceof PsiField ||
232            element instanceof PsiMethod;
233   }
234
235   /**
236    * @param expression
237    * @return loop body if expression is part of some loop's condition or for loop's increment part
238    *         null otherwise
239    */
240   public static PsiElement getLoopForLoopCondition(PsiExpression expression) {
241     PsiExpression outermost = expression;
242     while (outermost.getParent() instanceof PsiExpression) {
243       outermost = (PsiExpression)outermost.getParent();
244     }
245     if (outermost.getParent() instanceof PsiForStatement) {
246       final PsiForStatement forStatement = (PsiForStatement)outermost.getParent();
247       if (forStatement.getCondition() == outermost) {
248         return forStatement;
249       }
250       else {
251         return null;
252       }
253     }
254     if (outermost.getParent() instanceof PsiExpressionStatement && outermost.getParent().getParent() instanceof PsiForStatement) {
255       final PsiForStatement forStatement = (PsiForStatement)outermost.getParent().getParent();
256       if (forStatement.getUpdate() == outermost.getParent()) {
257         return forStatement;
258       }
259       else {
260         return null;
261       }
262     }
263     if (outermost.getParent() instanceof PsiWhileStatement) {
264       return outermost.getParent();
265     }
266     if (outermost.getParent() instanceof PsiDoWhileStatement) {
267       return outermost.getParent();
268     }
269     return null;
270   }
271
272   public static PsiClass getThisResolveClass(final PsiReferenceExpression place) {
273     final JavaResolveResult resolveResult = place.advancedResolve(false);
274     final PsiElement scope = resolveResult.getCurrentFileResolveScope();
275     if (scope instanceof PsiClass) {
276       return (PsiClass)scope;
277     }
278     return null;
279   }
280
281   public static PsiCall getEnclosingConstructorCall(PsiJavaCodeReferenceElement ref) {
282     PsiElement parent = ref.getParent();
283     if (ref instanceof PsiReferenceExpression && parent instanceof PsiMethodCallExpression) return (PsiCall)parent;
284
285     if (parent instanceof PsiAnonymousClass) {
286       parent = parent.getParent();
287     }
288
289     return parent instanceof PsiNewExpression ? (PsiNewExpression)parent : null;
290   }
291
292   public static PsiMethod getEnclosingMethod(PsiElement element) {
293     final PsiElement container = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiClass.class, PsiLambdaExpression.class);
294     return container instanceof PsiMethod ? (PsiMethod)container : null;
295   }
296
297   public static void renameVariableReferences(PsiVariable variable, String newName, SearchScope scope) throws IncorrectOperationException {
298     renameVariableReferences(variable, newName, scope, false);
299   }
300
301   public static void renameVariableReferences(PsiVariable variable,
302                                               String newName,
303                                               SearchScope scope,
304                                               final boolean ignoreAccessScope) throws IncorrectOperationException {
305     for (PsiReference reference : ReferencesSearch.search(variable, scope, ignoreAccessScope)) {
306       reference.handleElementRename(newName);
307     }
308   }
309
310   public static boolean canBeDeclaredFinal(@NotNull PsiVariable variable) {
311     LOG.assertTrue(variable instanceof PsiLocalVariable || variable instanceof PsiParameter);
312     final boolean isReassigned = HighlightControlFlowUtil
313       .isReassigned(variable, new THashMap<>());
314     return !isReassigned;
315   }
316
317   /**
318    * removes a reference to the specified class from the reference list given
319    *
320    * @return if removed  - a reference to the class or null if there were no references to this class in the reference list
321    */
322   public static PsiJavaCodeReferenceElement removeFromReferenceList(PsiReferenceList refList, PsiClass aClass)
323     throws IncorrectOperationException {
324     PsiJavaCodeReferenceElement[] refs = refList.getReferenceElements();
325     for (PsiJavaCodeReferenceElement ref : refs) {
326       if (ref.isReferenceTo(aClass)) {
327         PsiJavaCodeReferenceElement refCopy = (PsiJavaCodeReferenceElement)ref.copy();
328         ref.delete();
329         return refCopy;
330       }
331     }
332     return null;
333   }
334
335   public static PsiJavaCodeReferenceElement findReferenceToClass(PsiReferenceList refList, PsiClass aClass) {
336     PsiJavaCodeReferenceElement[] refs = refList.getReferenceElements();
337     for (PsiJavaCodeReferenceElement ref : refs) {
338       if (ref.isReferenceTo(aClass)) {
339         return ref;
340       }
341     }
342     return null;
343   }
344
345   public static PsiType getTypeByExpressionWithExpectedType(PsiExpression expr) {
346     PsiElementFactory factory = JavaPsiFacade.getElementFactory(expr.getProject());
347     PsiType typeByExpression = getTypeByExpression(expr, factory);
348     PsiType type = typeByExpression;
349     final boolean isFunctionalType = LambdaUtil.notInferredType(type);
350     PsiType exprType = expr.getType();
351     final boolean detectConjunct = exprType instanceof PsiIntersectionType ||
352                                    exprType instanceof PsiWildcardType && ((PsiWildcardType)exprType).getBound() instanceof PsiIntersectionType ||
353                                    exprType instanceof PsiCapturedWildcardType && ((PsiCapturedWildcardType)exprType).getUpperBound() instanceof PsiIntersectionType;
354     if (type != null && !isFunctionalType && !detectConjunct) {
355       return type;
356     }
357     ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getExpectedTypes(expr, false);
358     if (expectedTypes.length == 1 || (isFunctionalType || detectConjunct) && expectedTypes.length > 0 ) {
359       if (typeByExpression != null && Arrays.stream(expectedTypes).anyMatch(typeInfo -> typeByExpression.isAssignableFrom(typeInfo.getType()))) {
360         return type;
361       }
362       type = expectedTypes[0].getType();
363       if (!type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) return type;
364     }
365     return detectConjunct ? type : null;
366   }
367
368   public static PsiType getTypeByExpression(PsiExpression expr) {
369     PsiElementFactory factory = JavaPsiFacade.getElementFactory(expr.getProject());
370     PsiType type = getTypeByExpression(expr, factory);
371     if (LambdaUtil.notInferredType(type)) {
372       type = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, expr.getResolveScope());
373     }
374     return type;
375   }
376
377   private static PsiType getTypeByExpression(PsiExpression expr, final PsiElementFactory factory) {
378     PsiType type = RefactoringChangeUtil.getTypeByExpression(expr);
379     if (PsiType.NULL.equals(type)) {
380       ExpectedTypeInfo[] infos = ExpectedTypesProvider.getExpectedTypes(expr, false);
381       if (infos.length > 0) {
382         type = infos[0].getType();
383         if (type instanceof PsiPrimitiveType) {
384           type = ((PsiPrimitiveType)type).getBoxedType(expr);
385         }
386       }
387       else {
388         type = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, expr.getResolveScope());
389       }
390     }
391
392     return type;
393   }
394
395   public static boolean isAssignmentLHS(@NotNull PsiElement element) {
396     return element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression)element);
397   }
398
399   private static void removeFinalParameters(PsiMethod method) throws IncorrectOperationException {
400     PsiParameterList paramList = method.getParameterList();
401     PsiParameter[] params = paramList.getParameters();
402
403     for (PsiParameter param : params) {
404       if (param.hasModifierProperty(PsiModifier.FINAL)) {
405         PsiUtil.setModifierProperty(param, PsiModifier.FINAL, false);
406       }
407     }
408   }
409
410   public static PsiElement getAnchorElementForMultipleExpressions(@NotNull PsiExpression[] occurrences, PsiElement scope) {
411     PsiElement anchor = null;
412     for (PsiExpression occurrence : occurrences) {
413       if (scope != null && !PsiTreeUtil.isAncestor(scope, occurrence, false)) {
414         continue;
415       }
416       PsiElement anchor1 = getParentExpressionAnchorElement(occurrence);
417
418       if (anchor1 == null) {
419         if (occurrence.isPhysical()) return null;
420         continue;
421       }
422
423       if (anchor == null) {
424         anchor = anchor1;
425       }
426       else {
427         PsiElement commonParent = PsiTreeUtil.findCommonParent(anchor, anchor1);
428         if (commonParent == null || anchor.getTextRange() == null || anchor1.getTextRange() == null) return null;
429         PsiElement firstAnchor = anchor.getTextRange().getStartOffset() < anchor1.getTextRange().getStartOffset() ? anchor : anchor1;
430         if (commonParent.equals(firstAnchor)) {
431           anchor = firstAnchor;
432         }
433         else {
434           if (commonParent instanceof PsiStatement) {
435             anchor = commonParent;
436           }
437           else {
438             PsiElement parent = firstAnchor;
439             while (!parent.getParent().equals(commonParent)) {
440               parent = parent.getParent();
441             }
442             final PsiElement newAnchor = getParentExpressionAnchorElement(parent);
443             if (newAnchor != null) {
444               anchor = newAnchor;
445             }
446             else {
447               anchor = parent;
448             }
449           }
450         }
451       }
452     }
453
454     if (anchor == null) return null;
455     if (occurrences.length > 1 && anchor.getParent().getParent() instanceof PsiSwitchStatement) {
456       PsiSwitchStatement switchStatement = (PsiSwitchStatement)anchor.getParent().getParent();
457       if (switchStatement.getBody().equals(anchor.getParent())) {
458         int startOffset = occurrences[0].getTextRange().getStartOffset();
459         int endOffset = occurrences[occurrences.length - 1].getTextRange().getEndOffset();
460         PsiStatement[] statements = switchStatement.getBody().getStatements();
461         boolean isInDifferentCases = false;
462         for (PsiStatement statement : statements) {
463           if (statement instanceof PsiSwitchLabelStatement) {
464             int caseOffset = statement.getTextRange().getStartOffset();
465             if (startOffset < caseOffset && caseOffset < endOffset) {
466               isInDifferentCases = true;
467               break;
468             }
469           }
470         }
471         if (isInDifferentCases) {
472           anchor = switchStatement;
473         }
474       }
475     }
476
477     return anchor;
478   }
479
480   public static boolean isMethodUsage(PsiElement element) {
481     if (element instanceof PsiEnumConstant) {
482       return JavaLanguage.INSTANCE.equals(element.getLanguage());
483     }
484     if (!(element instanceof PsiJavaCodeReferenceElement)) return false;
485     PsiElement parent = element.getParent();
486     if (parent instanceof PsiCall) {
487       return true;
488     }
489     else if (parent instanceof PsiAnonymousClass) {
490       return element.equals(((PsiAnonymousClass)parent).getBaseClassReference());
491     }
492     return false;
493   }
494
495   @Nullable
496   public static PsiExpressionList getArgumentListByMethodReference(PsiElement ref) {
497     if (ref instanceof PsiCall) return ((PsiCall)ref).getArgumentList();
498     PsiElement parent = ref.getParent();
499     if (parent instanceof PsiCall) {
500       return ((PsiCall)parent).getArgumentList();
501     }
502     else if (parent instanceof PsiAnonymousClass) {
503       return ((PsiNewExpression)parent.getParent()).getArgumentList();
504     }
505     LOG.assertTrue(false);
506     return null;
507   }
508
509   public static PsiCall getCallExpressionByMethodReference(PsiElement ref) {
510     if (ref instanceof PsiCall) return (PsiCall)ref;
511     PsiElement parent = ref.getParent();
512     if (parent instanceof PsiMethodCallExpression) {
513       return (PsiMethodCallExpression)parent;
514     }
515     else if (parent instanceof PsiNewExpression) {
516       return (PsiNewExpression)parent;
517     }
518     else if (parent instanceof PsiAnonymousClass) {
519       return (PsiNewExpression)parent.getParent();
520     }
521     else {
522       LOG.assertTrue(false);
523       return null;
524     }
525   }
526
527   /**
528    * @return List of highlighters
529    */
530   public static List<RangeHighlighter> highlightAllOccurrences(Project project, PsiElement[] occurrences, Editor editor) {
531     ArrayList<RangeHighlighter> highlighters = new ArrayList<>();
532     HighlightManager highlightManager = HighlightManager.getInstance(project);
533     EditorColorsManager colorsManager = EditorColorsManager.getInstance();
534     TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
535     if (occurrences.length > 1) {
536       for (PsiElement occurrence : occurrences) {
537         final RangeMarker rangeMarker = occurrence.getUserData(ElementToWorkOn.TEXT_RANGE);
538         if (rangeMarker != null && rangeMarker.isValid()) {
539           highlightManager
540             .addRangeHighlight(editor, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), attributes, true, highlighters);
541         }
542         else {
543           final TextRange textRange = occurrence.getTextRange();
544           highlightManager.addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, true, highlighters);
545         }
546       }
547     }
548     return highlighters;
549   }
550
551   public static String createTempVar(PsiExpression expr, PsiElement context, boolean declareFinal) throws IncorrectOperationException {
552     PsiElement anchorStatement = getParentStatement(context, true);
553     LOG.assertTrue(anchorStatement != null && anchorStatement.getParent() != null);
554
555     Project project = expr.getProject();
556     String[] suggestedNames =
557       JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expr, null).names;
558     final String prefix = suggestedNames.length > 0 ? suggestedNames[0] : "var";
559     final String id = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(prefix, context, true);
560
561     PsiElementFactory factory = JavaPsiFacade.getElementFactory(expr.getProject());
562
563     if (expr instanceof PsiParenthesizedExpression) {
564       PsiExpression expr1 = ((PsiParenthesizedExpression)expr).getExpression();
565       if (expr1 != null) {
566         expr = expr1;
567       }
568     }
569     PsiDeclarationStatement decl = factory.createVariableDeclarationStatement(id, expr.getType(), expr);
570     if (declareFinal) {
571       PsiUtil.setModifierProperty(((PsiLocalVariable)decl.getDeclaredElements()[0]), PsiModifier.FINAL, true);
572     }
573     anchorStatement.getParent().addBefore(decl, anchorStatement);
574
575     return id;
576   }
577
578   public static int verifySafeCopyExpression(PsiElement expr) {
579     return verifySafeCopyExpressionSubElement(expr);
580
581   }
582
583   private static int verifySafeCopyExpressionSubElement(PsiElement element) {
584     int result = EXPR_COPY_SAFE;
585     if (element == null) return result;
586
587     if (element instanceof PsiThisExpression || element instanceof PsiSuperExpression || element instanceof PsiIdentifier) {
588       return EXPR_COPY_SAFE;
589     }
590
591     if (element instanceof PsiMethodCallExpression) {
592       result = EXPR_COPY_UNSAFE;
593     }
594
595     if (element instanceof PsiNewExpression) {
596       return EXPR_COPY_PROHIBITED;
597     }
598
599     if (element instanceof PsiAssignmentExpression) {
600       return EXPR_COPY_PROHIBITED;
601     }
602
603     if (PsiUtil.isIncrementDecrementOperation(element)) {
604       return EXPR_COPY_PROHIBITED;
605     }
606
607     PsiElement[] children = element.getChildren();
608
609     for (PsiElement child : children) {
610       int childResult = verifySafeCopyExpressionSubElement(child);
611       result = Math.max(result, childResult);
612     }
613     return result;
614   }
615
616   @Contract("null, _ -> null")
617   public static PsiExpression convertInitializerToNormalExpression(PsiExpression expression, PsiType forcedReturnType)
618     throws IncorrectOperationException {
619     if (expression instanceof PsiArrayInitializerExpression && (forcedReturnType == null || forcedReturnType instanceof PsiArrayType)) {
620       return createNewExpressionFromArrayInitializer((PsiArrayInitializerExpression)expression, forcedReturnType);
621     }
622     return expression;
623   }
624
625   public static PsiExpression createNewExpressionFromArrayInitializer(PsiArrayInitializerExpression initializer, PsiType forcedType)
626     throws IncorrectOperationException {
627     PsiType initializerType = null;
628     if (initializer != null) {
629       if (forcedType != null) {
630         initializerType = forcedType;
631       }
632       else {
633         initializerType = getTypeByExpression(initializer);
634       }
635     }
636     if (initializerType == null) {
637       return initializer;
638     }
639     LOG.assertTrue(initializerType instanceof PsiArrayType);
640     PsiElementFactory factory = JavaPsiFacade.getElementFactory(initializer.getProject());
641     PsiNewExpression result =
642       (PsiNewExpression)factory.createExpressionFromText("new " + initializerType.getPresentableText() + "{}", null);
643     result = (PsiNewExpression)CodeStyleManager.getInstance(initializer.getProject()).reformat(result);
644     PsiArrayInitializerExpression arrayInitializer = result.getArrayInitializer();
645     LOG.assertTrue(arrayInitializer != null);
646     arrayInitializer.replace(initializer);
647     return result;
648   }
649
650   public static void makeMethodAbstract(@NotNull PsiClass targetClass, @NotNull PsiMethod method) throws IncorrectOperationException {
651     if (!method.hasModifierProperty(PsiModifier.DEFAULT)) {
652       PsiCodeBlock body = method.getBody();
653       if (body != null) {
654         body.delete();
655       }
656
657       PsiUtil.setModifierProperty(method, PsiModifier.ABSTRACT, true);
658     }
659
660     if (!targetClass.isInterface()) {
661       PsiUtil.setModifierProperty(targetClass, PsiModifier.ABSTRACT, true);
662       prepareForAbstract(method);
663     }
664     else {
665       prepareForInterface(method);
666     }
667
668   }
669
670   public static void makeMethodDefault(@NotNull PsiMethod method) throws IncorrectOperationException {
671     PsiUtil.setModifierProperty(method, PsiModifier.DEFAULT, !method.hasModifierProperty(PsiModifier.STATIC));
672     PsiUtil.setModifierProperty(method, PsiModifier.ABSTRACT, false);
673
674     prepareForInterface(method);
675   }
676
677   private static void prepareForInterface(PsiMethod method) {
678     PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, false);
679     PsiUtil.setModifierProperty(method, PsiModifier.PRIVATE, false);
680     PsiUtil.setModifierProperty(method, PsiModifier.PROTECTED, false);
681     prepareForAbstract(method);
682   }
683
684   private static void prepareForAbstract(PsiMethod method) {
685     PsiUtil.setModifierProperty(method, PsiModifier.FINAL, false);
686     PsiUtil.setModifierProperty(method, PsiModifier.SYNCHRONIZED, false);
687     PsiUtil.setModifierProperty(method, PsiModifier.NATIVE, false);
688     removeFinalParameters(method);
689   }
690
691   public static boolean isInsideAnonymousOrLocal(PsiElement element, PsiElement upTo) {
692     for (PsiElement current = element; current != null && current != upTo; current = current.getParent()) {
693       if (current instanceof PsiAnonymousClass) return true;
694       if (current instanceof PsiClass && current.getParent() instanceof PsiDeclarationStatement) {
695         return true;
696       }
697     }
698     return false;
699   }
700
701   public static PsiExpression unparenthesizeExpression(PsiExpression expression) {
702     while (expression instanceof PsiParenthesizedExpression) {
703       final PsiExpression innerExpression = ((PsiParenthesizedExpression)expression).getExpression();
704       if (innerExpression == null) return expression;
705       expression = innerExpression;
706     }
707     return expression;
708   }
709
710   public static PsiExpression outermostParenthesizedExpression(PsiExpression expression) {
711     while (expression.getParent() instanceof PsiParenthesizedExpression) {
712       expression = (PsiParenthesizedExpression)expression.getParent();
713     }
714     return expression;
715   }
716
717   public static String getNewInnerClassName(PsiClass aClass, String oldInnerClassName, String newName) {
718     String className = aClass.getName();
719     if (className == null || !oldInnerClassName.endsWith(className)) return newName;
720     StringBuilder buffer = new StringBuilder(oldInnerClassName);
721     buffer.replace(buffer.length() - className.length(), buffer.length(), newName);
722     return buffer.toString();
723   }
724
725   public static void visitImplicitSuperConstructorUsages(PsiClass subClass,
726                                                          final ImplicitConstructorUsageVisitor implicitConstructorUsageVistor,
727                                                          PsiClass superClass) {
728     final PsiMethod baseDefaultConstructor = findDefaultConstructor(superClass);
729     final PsiMethod[] constructors = subClass.getConstructors();
730     if (constructors.length > 0) {
731       for (PsiMethod constructor : constructors) {
732         PsiCodeBlock body = constructor.getBody();
733         if (body == null) continue;
734         final PsiStatement[] statements = body.getStatements();
735         if (statements.length < 1 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true)) {
736           implicitConstructorUsageVistor.visitConstructor(constructor, baseDefaultConstructor);
737         }
738       }
739     }
740     else {
741       implicitConstructorUsageVistor.visitClassWithoutConstructors(subClass);
742     }
743   }
744
745   private static PsiMethod findDefaultConstructor(final PsiClass aClass) {
746     final PsiMethod[] constructors = aClass.getConstructors();
747     for (PsiMethod constructor : constructors) {
748       if (constructor.getParameterList().isEmpty()) return constructor;
749     }
750
751     return null;
752   }
753
754   public static void replaceMovedMemberTypeParameters(final PsiElement member,
755                                                       final Iterable<? extends PsiTypeParameter> parametersIterable,
756                                                       final PsiSubstitutor substitutor,
757                                                       final PsiElementFactory factory) {
758     final Map<PsiElement, PsiElement> replacement = new LinkedHashMap<>();
759     for (PsiTypeParameter parameter : parametersIterable) {
760       final PsiType substitutedType = substitutor.substitute(parameter);
761       final PsiType erasedType = substitutedType == null ? TypeConversionUtil.erasure(factory.createType(parameter))
762                                                          : substitutedType;
763       for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(member))) {
764         final PsiElement element = reference.getElement();
765         final PsiElement parent = element.getParent();
766         if (parent instanceof PsiTypeElement) {
767           if (substitutedType == null) {
768             //extends/implements list of type parameters: S extends List<T>
769             final PsiJavaCodeReferenceElement codeReferenceElement = PsiTreeUtil.getTopmostParentOfType(parent, PsiJavaCodeReferenceElement.class);
770             if (codeReferenceElement != null) {
771               final PsiJavaCodeReferenceElement copy = (PsiJavaCodeReferenceElement)codeReferenceElement.copy();
772               final PsiReferenceParameterList parameterList = copy.getParameterList();
773               if (parameterList != null) {
774                 parameterList.delete();
775               }
776               replacement.put(codeReferenceElement, copy);
777             }
778             else {
779               //nested types List<List<T> listOfLists;
780               PsiTypeElement topPsiTypeElement = PsiTreeUtil.getTopmostParentOfType(parent, PsiTypeElement.class);
781               if (topPsiTypeElement == null) {
782                 topPsiTypeElement = (PsiTypeElement)parent;
783               }
784               replacement.put(topPsiTypeElement, factory.createTypeElement(TypeConversionUtil.erasure(topPsiTypeElement.getType())));
785             }
786           }
787           else {
788             replacement.put(parent, factory.createTypeElement(substitutedType));
789           }
790         }
791         else if (element instanceof PsiJavaCodeReferenceElement && erasedType instanceof PsiClassType) {
792           replacement.put(element, factory.createReferenceElementByType((PsiClassType)erasedType));
793         }
794       }
795     }
796     for (PsiElement element : replacement.keySet()) {
797       if (element.isValid()) {
798         element.replace(replacement.get(element));
799       }
800     }
801   }
802
803   public static void renameConflictingTypeParameters(PsiMember memberCopy, PsiClass targetClass) {
804     if (memberCopy instanceof PsiTypeParameterListOwner && !memberCopy.hasModifierProperty(PsiModifier.STATIC)) {
805       UniqueNameGenerator nameGenerator = new UniqueNameGenerator();
806       PsiUtil.typeParametersIterable(targetClass).forEach(param -> {
807         String paramName = param.getName();
808         if (paramName != null) {
809           nameGenerator.addExistingName(paramName);
810         }
811       });
812
813       PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
814       PsiElementFactory factory = JavaPsiFacade.getElementFactory(memberCopy.getProject());
815       for (PsiTypeParameter parameter : ((PsiTypeParameterListOwner)memberCopy).getTypeParameters()) {
816         String parameterName = parameter.getName();
817         if (parameterName == null) continue;
818         if (!nameGenerator.isUnique(parameterName)) {
819           substitutor = substitutor.put(parameter, PsiSubstitutor.EMPTY.substitute(factory.createTypeParameter(nameGenerator.generateUniqueName(parameterName), PsiClassType.EMPTY_ARRAY)));
820         }
821       }
822       if (!PsiSubstitutor.EMPTY.equals(substitutor)) {
823         replaceMovedMemberTypeParameters(memberCopy, PsiUtil.typeParametersIterable((PsiTypeParameterListOwner)memberCopy), substitutor, factory);//rename usages in the method
824         for (Map.Entry<PsiTypeParameter, PsiType> entry : substitutor.getSubstitutionMap().entrySet()) {
825           entry.getKey().setName(entry.getValue().getCanonicalText()); //rename declaration after all usages renamed
826         }
827       }
828     }
829   }
830
831   @Nullable
832   public static PsiMethod getChainedConstructor(PsiMethod constructor) {
833     final PsiCodeBlock constructorBody = constructor.getBody();
834     if (constructorBody == null) return null;
835     final PsiStatement[] statements = constructorBody.getStatements();
836     if (statements.length == 1 && statements[0] instanceof PsiExpressionStatement) {
837       final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
838       if (expression instanceof PsiMethodCallExpression) {
839         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
840         final PsiReferenceExpression methodExpr = methodCallExpression.getMethodExpression();
841         if ("this".equals(methodExpr.getReferenceName())) {
842           return (PsiMethod)methodExpr.resolve();
843         }
844       }
845     }
846     return null;
847   }
848
849   public static boolean isInMovedElement(PsiElement element, Set<? extends PsiMember> membersToMove) {
850     for (PsiMember member : membersToMove) {
851       if (PsiTreeUtil.isAncestor(member, element, false)) return true;
852     }
853     return false;
854   }
855
856   public static boolean inImportStatement(PsiReference ref, PsiElement element) {
857     if (PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) return true;
858     final PsiFile containingFile = element.getContainingFile();
859     if (containingFile instanceof PsiJavaFile) {
860       final PsiImportList importList = ((PsiJavaFile)containingFile).getImportList();
861       if (importList != null) {
862         final TextRange refRange = ref.getRangeInElement().shiftRight(element.getTextRange().getStartOffset());
863         for (PsiImportStatementBase importStatementBase : importList.getAllImportStatements()) {
864           final TextRange textRange = importStatementBase.getTextRange();
865           if (textRange.contains(refRange)) {
866             return true;
867           }
868         }
869       }
870     }
871     return false;
872   }
873
874   public static PsiStatement putStatementInLoopBody(PsiStatement declaration,
875                                                   PsiElement container,
876                                                   PsiElement finalAnchorStatement) throws IncorrectOperationException {
877     return putStatementInLoopBody(declaration, container, finalAnchorStatement, false);
878   }
879
880   public static PsiStatement putStatementInLoopBody(PsiStatement declaration,
881                                                     PsiElement container,
882                                                     PsiElement finalAnchorStatement,
883                                                     boolean replaceBody)
884     throws IncorrectOperationException {
885     final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(container.getProject());
886     if(isLoopOrIf(container)) {
887       PsiStatement loopBody = getLoopBody(container, finalAnchorStatement);
888       PsiStatement loopBodyCopy = loopBody != null ? (PsiStatement) loopBody.copy() : null;
889       PsiBlockStatement blockStatement = (PsiBlockStatement)elementFactory
890         .createStatementFromText("{}", null);
891       blockStatement = (PsiBlockStatement) CodeStyleManager.getInstance(container.getProject()).reformat(blockStatement);
892       final PsiElement prevSibling = loopBody.getPrevSibling();
893       if(prevSibling instanceof PsiWhiteSpace) {
894         final PsiElement pprev = prevSibling.getPrevSibling();
895         if (!(pprev instanceof PsiComment) || !((PsiComment)pprev).getTokenType().equals(JavaTokenType.END_OF_LINE_COMMENT)) {
896           prevSibling.delete();
897         }
898       }
899       blockStatement = (PsiBlockStatement) loopBody.replace(blockStatement);
900       final PsiCodeBlock codeBlock = blockStatement.getCodeBlock();
901       declaration = (PsiStatement) codeBlock.add(declaration);
902       JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration);
903       if (loopBodyCopy != null && !replaceBody) codeBlock.add(loopBodyCopy);
904     } else if (container instanceof PsiLambdaExpression) {
905       PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)container;
906       final PsiElement invalidBody = lambdaExpression.getBody();
907       if (invalidBody == null) return declaration;
908
909       String lambdaParamListWithArrowAndComments = lambdaExpression.getText()
910         .substring(0, (declaration.isPhysical() ? declaration : invalidBody).getStartOffsetInParent());
911       final PsiLambdaExpression expressionFromText = (PsiLambdaExpression)elementFactory.createExpressionFromText(lambdaParamListWithArrowAndComments + "{}", lambdaExpression.getParent());
912       PsiCodeBlock newBody = (PsiCodeBlock)expressionFromText.getBody();
913       LOG.assertTrue(newBody != null);
914       newBody.add(declaration);
915
916       final PsiElement lambdaExpressionBody = lambdaExpression.getBody();
917       LOG.assertTrue(lambdaExpressionBody != null);
918       final PsiStatement lastBodyStatement;
919       if (PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression))) {
920         if (replaceBody) {
921           lastBodyStatement = null;
922         } else {
923           lastBodyStatement = elementFactory.createStatementFromText("a;", lambdaExpression);
924           ((PsiExpressionStatement)lastBodyStatement).getExpression().replace(lambdaExpressionBody);
925         }
926       }
927       else {
928         lastBodyStatement = elementFactory.createStatementFromText("return a;", lambdaExpression);
929         final PsiExpression returnValue = ((PsiReturnStatement)lastBodyStatement).getReturnValue();
930         LOG.assertTrue(returnValue != null);
931         returnValue.replace(lambdaExpressionBody);
932       }
933       if (lastBodyStatement != null) {
934         newBody.add(lastBodyStatement);
935       }
936
937       final PsiLambdaExpression copy = (PsiLambdaExpression)lambdaExpression.replace(expressionFromText);
938       newBody = (PsiCodeBlock)copy.getBody();
939       LOG.assertTrue(newBody != null);
940       declaration = newBody.getStatements()[0];
941       declaration = (PsiStatement)JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration);
942     }
943     return declaration;
944   }
945
946   @Nullable
947   private static PsiStatement getLoopBody(PsiElement container, PsiElement anchorStatement) {
948     if(container instanceof PsiLoopStatement) {
949       return ((PsiLoopStatement) container).getBody();
950     }
951     else if (container instanceof PsiIfStatement) {
952       final PsiStatement thenBranch = ((PsiIfStatement)container).getThenBranch();
953       if (thenBranch != null && PsiTreeUtil.isAncestor(thenBranch, anchorStatement, false)) {
954         return thenBranch;
955       }
956       final PsiStatement elseBranch = ((PsiIfStatement)container).getElseBranch();
957       if (elseBranch != null && PsiTreeUtil.isAncestor(elseBranch, anchorStatement, false)) {
958         return elseBranch;
959       }
960       LOG.assertTrue(false);
961     }
962     LOG.assertTrue(false);
963     return null;
964   }
965
966   public static boolean isLoopOrIf(PsiElement element) {
967     return element instanceof PsiLoopStatement || element instanceof PsiIfStatement;
968   }
969
970   public static PsiCodeBlock expandExpressionLambdaToCodeBlock(@NotNull PsiLambdaExpression lambdaExpression) {
971     final PsiElement body = lambdaExpression.getBody();
972     if (!(body instanceof PsiExpression)) return (PsiCodeBlock)body;
973
974     String newLambdaText = "{";
975     if (!PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression))) newLambdaText += "return ";
976     newLambdaText += "a;}";
977
978     final Project project = lambdaExpression.getProject();
979     final PsiCodeBlock codeBlock = JavaPsiFacade.getElementFactory(project).createCodeBlockFromText(newLambdaText, lambdaExpression);
980     PsiStatement statement = codeBlock.getStatements()[0];
981     if (statement instanceof PsiReturnStatement) {
982       PsiExpression value = ((PsiReturnStatement)statement).getReturnValue();
983       LOG.assertTrue(value != null);
984       value.replace(body);
985     }
986     else if (statement instanceof PsiExpressionStatement){
987       ((PsiExpressionStatement)statement).getExpression().replace(body);
988     }
989     PsiElement arrow = PsiTreeUtil.skipWhitespacesBackward(body);
990     if (arrow != null && arrow.getNextSibling() != body) {
991       lambdaExpression.deleteChildRange(arrow.getNextSibling(), body.getPrevSibling());
992     }
993     return (PsiCodeBlock)CodeStyleManager.getInstance(project).reformat(body.replace(codeBlock));
994   }
995
996   /**
997    * Ensures that given call is surrounded by {@link PsiCodeBlock} (that is, it has a parent statement
998    * which is located inside the code block). If not, tries to create a code block.
999    *
1000    * <p>
1001    * Note that the expression is not necessarily a child of {@link PsiExpressionStatement}; it could be a subexpression,
1002    * {@link PsiIfStatement}, etc.
1003    * </p>
1004    *
1005    * <p>
1006    * This method works if {@link com.siyeh.ig.psiutils.ControlFlowUtils#canExtractStatement(PsiExpression)}
1007    * returns true on the same expression.
1008    * </p>
1009    *
1010    * @param expression an expression which should be located inside the code block
1011    * @return a passed expression if it's already surrounded by code block and no changes are necessary;
1012    *         a replacement expression (which is equivalent to the passed expression) if a new code block was created;
1013    *         {@code null} if the expression cannot be surrounded with code block.
1014    */
1015   @Nullable
1016   public static <T extends PsiExpression> T ensureCodeBlock(@NotNull T expression) {
1017     return EnsureCodeBlockImpl.ensureCodeBlock(expression);
1018   }
1019
1020   public static String checkEnumConstantInSwitchLabel(PsiExpression expr) {
1021     if (PsiImplUtil.getSwitchLabel(expr) != null) {
1022       PsiReferenceExpression ref = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expr), PsiReferenceExpression.class);
1023       if (ref != null && ref.resolve() instanceof PsiEnumConstant) {
1024         return RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("refactoring.introduce.variable.enum.in.label.message"));
1025       }
1026     }
1027     return null;
1028   }
1029
1030   public interface ImplicitConstructorUsageVisitor {
1031     void visitConstructor(PsiMethod constructor, PsiMethod baseConstructor);
1032
1033     void visitClassWithoutConstructors(PsiClass aClass);
1034   }
1035
1036   public interface Graph<T> {
1037     Set<T> getVertices();
1038
1039     Set<T> getTargets(T source);
1040   }
1041
1042   /**
1043    * Returns subset of {@code graph.getVertices()} that is a transitive closure (by <code>graph.getTargets()<code>)
1044    * of the following property: initialRelation.value() of vertex or {@code graph.getTargets(vertex)} is true.
1045    * <p/>
1046    * Note that {@code graph.getTargets()} is not neccesrily a subset of {@code graph.getVertex()}
1047    *
1048    * @param graph
1049    * @param initialRelation
1050    * @return subset of graph.getVertices()
1051    */
1052   public static <T> Set<T> transitiveClosure(Graph<T> graph, Condition<? super T> initialRelation) {
1053     Set<T> result = new HashSet<>();
1054
1055     final Set<T> vertices = graph.getVertices();
1056     boolean anyChanged;
1057     do {
1058       anyChanged = false;
1059       for (T currentVertex : vertices) {
1060         if (!result.contains(currentVertex)) {
1061           if (!initialRelation.value(currentVertex)) {
1062             Set<T> targets = graph.getTargets(currentVertex);
1063             for (T currentTarget : targets) {
1064               if (result.contains(currentTarget) || initialRelation.value(currentTarget)) {
1065                 result.add(currentVertex);
1066                 anyChanged = true;
1067                 break;
1068               }
1069             }
1070           }
1071           else {
1072             result.add(currentVertex);
1073           }
1074         }
1075       }
1076     }
1077     while (anyChanged);
1078     return result;
1079   }
1080
1081   public static boolean equivalentTypes(PsiType t1, PsiType t2, PsiManager manager) {
1082     while (t1 instanceof PsiArrayType) {
1083       if (!(t2 instanceof PsiArrayType)) return false;
1084       t1 = ((PsiArrayType)t1).getComponentType();
1085       t2 = ((PsiArrayType)t2).getComponentType();
1086     }
1087
1088     if (t1 instanceof PsiPrimitiveType) {
1089       return t2 instanceof PsiPrimitiveType && t1.equals(t2);
1090     }
1091
1092     return manager.areElementsEquivalent(PsiUtil.resolveClassInType(t1), PsiUtil.resolveClassInType(t2));
1093   }
1094
1095   public static List<PsiVariable> collectReferencedVariables(PsiElement scope) {
1096     final List<PsiVariable> result = new ArrayList<>();
1097     scope.accept(new JavaRecursiveElementWalkingVisitor() {
1098       @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
1099         final PsiElement element = expression.resolve();
1100         if (element instanceof PsiVariable) {
1101           result.add((PsiVariable)element);
1102         }
1103         final PsiExpression qualifier = expression.getQualifierExpression();
1104         if (qualifier != null) {
1105           qualifier.accept(this);
1106         }
1107       }
1108     });
1109     return result;
1110   }
1111
1112   public static boolean isModifiedInScope(PsiVariable variable, PsiElement scope) {
1113     for (PsiReference reference : ReferencesSearch.search(variable, new LocalSearchScope(scope), false)) {
1114       if (isAssignmentLHS(reference.getElement())) return true;
1115     }
1116     return false;
1117   }
1118
1119   private static String getNameOfReferencedParameter(PsiDocTag tag) {
1120     LOG.assertTrue("param".equals(tag.getName()));
1121     final PsiElement[] dataElements = tag.getDataElements();
1122     if (dataElements.length < 1) return null;
1123     return dataElements[0].getText();
1124   }
1125
1126   public static void fixJavadocsForParams(PsiMethod method, Set<? extends PsiParameter> newParameters) throws IncorrectOperationException {
1127     fixJavadocsForParams(method, newParameters, Conditions.alwaysFalse());
1128   }
1129
1130   public static void fixJavadocsForParams(PsiMethod method,
1131                                           Set<? extends PsiParameter> newParameters,
1132                                           Condition<? super Pair<PsiParameter, String>> eqCondition) throws IncorrectOperationException {
1133     fixJavadocsForParams(method, newParameters, eqCondition, Conditions.alwaysTrue());
1134   }
1135
1136   public static void fixJavadocsForParams(PsiMethod method,
1137                                           Set<? extends PsiParameter> newParameters,
1138                                           Condition<? super Pair<PsiParameter, String>> eqCondition,
1139                                           Condition<? super String> matchedToOldParam) throws IncorrectOperationException {
1140     final PsiDocComment docComment = method.getDocComment();
1141     if (docComment == null) return;
1142     final PsiParameter[] parameters = method.getParameterList().getParameters();
1143     final PsiDocTag[] paramTags = docComment.findTagsByName("param");
1144     if (parameters.length > 0 && newParameters.size() < parameters.length && paramTags.length == 0) return;
1145     Map<PsiParameter, PsiDocTag> tagForParam = new HashMap<>();
1146     for (PsiParameter parameter : parameters) {
1147       boolean found = false;
1148       for (PsiDocTag paramTag : paramTags) {
1149         if (parameter.getName().equals(getNameOfReferencedParameter(paramTag))) {
1150           tagForParam.put(parameter, paramTag);
1151           found = true;
1152           break;
1153         }
1154       }
1155       if (!found) {
1156         for (PsiDocTag paramTag : paramTags) {
1157           final String paramName = getNameOfReferencedParameter(paramTag);
1158           if (eqCondition.value(Pair.create(parameter, paramName))) {
1159             tagForParam.put(parameter, paramTag);
1160             found = true;
1161             break;
1162           }
1163         }
1164       }
1165       if (!found && !newParameters.contains(parameter)) {
1166         tagForParam.put(parameter, null);
1167       }
1168     }
1169
1170     List<PsiDocTag> newTags = new ArrayList<>();
1171
1172     for (PsiDocTag paramTag : paramTags) {
1173       final String paramName = getNameOfReferencedParameter(paramTag);
1174       if (!tagForParam.containsValue(paramTag) && !matchedToOldParam.value(paramName)) {
1175         newTags.add((PsiDocTag)paramTag.copy());
1176       }
1177     }
1178
1179     for (PsiParameter parameter : parameters) {
1180       if (tagForParam.containsKey(parameter)) {
1181         final PsiDocTag psiDocTag = tagForParam.get(parameter);
1182         if (psiDocTag != null) {
1183           final PsiDocTag copy = (PsiDocTag)psiDocTag.copy();
1184           final PsiDocTagValue valueElement = copy.getValueElement();
1185           if (valueElement != null) {
1186             valueElement.replace(createParamTag(parameter).getValueElement());
1187           }
1188           newTags.add(copy);
1189         }
1190       }
1191       else {
1192         newTags.add(createParamTag(parameter));
1193       }
1194     }
1195     PsiElement anchor = paramTags.length > 0 ? paramTags[0].getPrevSibling() : null;
1196     for (PsiDocTag paramTag : paramTags) {
1197       paramTag.delete();
1198     }
1199     for (PsiDocTag psiDocTag : newTags) {
1200       anchor = anchor != null && anchor.isValid() ? docComment.addAfter(psiDocTag, anchor) : docComment.add(psiDocTag);
1201     }
1202   }
1203
1204   private static PsiDocTag createParamTag(PsiParameter parameter) {
1205     return JavaPsiFacade.getElementFactory(parameter.getProject()).createParamTag(parameter.getName(), "");
1206   }
1207
1208   public static PsiDirectory createPackageDirectoryInSourceRoot(PackageWrapper aPackage, final VirtualFile sourceRoot)
1209     throws IncorrectOperationException {
1210     final PsiDirectory[] directories = aPackage.getDirectories();
1211     for (PsiDirectory directory : directories) {
1212       if (VfsUtil.isAncestor(sourceRoot, directory.getVirtualFile(), false)) {
1213         return directory;
1214       }
1215     }
1216     String qNameToCreate = qNameToCreateInSourceRoot(aPackage, sourceRoot);
1217     final String[] shortNames = qNameToCreate.split("\\.");
1218     PsiDirectory current = aPackage.getManager().findDirectory(sourceRoot);
1219     LOG.assertTrue(current != null);
1220     for (String shortName : shortNames) {
1221       PsiDirectory subdirectory = current.findSubdirectory(shortName);
1222       if (subdirectory == null) {
1223         subdirectory = current.createSubdirectory(shortName);
1224       }
1225       current = subdirectory;
1226     }
1227     return current;
1228   }
1229
1230   public static String qNameToCreateInSourceRoot(PackageWrapper aPackage, final VirtualFile sourceRoot) throws IncorrectOperationException {
1231     String targetQName = aPackage.getQualifiedName();
1232     String sourceRootPackage =
1233       ProjectRootManager.getInstance(aPackage.getManager().getProject()).getFileIndex().getPackageNameByDirectory(sourceRoot);
1234     if (!canCreateInSourceRoot(sourceRootPackage, targetQName)) {
1235       throw new IncorrectOperationException(
1236         "Cannot create package '" + targetQName + "' in source folder " + sourceRoot.getPresentableUrl());
1237     }
1238     String result = targetQName.substring(sourceRootPackage.length());
1239     if (StringUtil.startsWithChar(result, '.')) result = result.substring(1);  // remove initial '.'
1240     return result;
1241   }
1242
1243   public static boolean canCreateInSourceRoot(final String sourceRootPackage, final String targetQName) {
1244     if (sourceRootPackage == null || !targetQName.startsWith(sourceRootPackage)) return false;
1245     if (sourceRootPackage.isEmpty() || targetQName.length() == sourceRootPackage.length()) return true;
1246     return targetQName.charAt(sourceRootPackage.length()) == '.';
1247   }
1248
1249
1250   @Nullable
1251   public static PsiDirectory findPackageDirectoryInSourceRoot(PackageWrapper aPackage, final VirtualFile sourceRoot) {
1252     final PsiDirectory[] directories = aPackage.getDirectories();
1253     for (PsiDirectory directory : directories) {
1254       if (VfsUtil.isAncestor(sourceRoot, directory.getVirtualFile(), false)) {
1255         return directory;
1256       }
1257     }
1258     String qNameToCreate;
1259     try {
1260       qNameToCreate = qNameToCreateInSourceRoot(aPackage, sourceRoot);
1261     }
1262     catch (IncorrectOperationException e) {
1263       return null;
1264     }
1265     final String[] shortNames = qNameToCreate.split("\\.");
1266     PsiDirectory current = aPackage.getManager().findDirectory(sourceRoot);
1267     LOG.assertTrue(current != null);
1268     for (String shortName : shortNames) {
1269       PsiDirectory subdirectory = current.findSubdirectory(shortName);
1270       if (subdirectory == null) {
1271         return null;
1272       }
1273       current = subdirectory;
1274     }
1275     return current;
1276   }
1277
1278   public static class ConditionCache<T> implements Condition<T> {
1279     private final Condition<? super T> myCondition;
1280     private final HashSet<T> myProcessedSet = new HashSet<>();
1281     private final HashSet<T> myTrueSet = new HashSet<>();
1282
1283     public ConditionCache(Condition<? super T> condition) {
1284       myCondition = condition;
1285     }
1286
1287     @Override
1288     public boolean value(T object) {
1289       if (!myProcessedSet.contains(object)) {
1290         myProcessedSet.add(object);
1291         final boolean value = myCondition.value(object);
1292         if (value) {
1293           myTrueSet.add(object);
1294           return true;
1295         }
1296         return false;
1297       }
1298       return myTrueSet.contains(object);
1299     }
1300   }
1301
1302   public static class IsDescendantOf implements Condition<PsiClass> {
1303     private final PsiClass myClass;
1304     private final ConditionCache<PsiClass> myConditionCache;
1305
1306     public IsDescendantOf(PsiClass aClass) {
1307       myClass = aClass;
1308       myConditionCache = new ConditionCache<>(aClass1 -> InheritanceUtil.isInheritorOrSelf(aClass1, myClass, true));
1309     }
1310
1311     @Override
1312     public boolean value(PsiClass aClass) {
1313       return myConditionCache.value(aClass);
1314     }
1315   }
1316
1317   @Nullable
1318   public static PsiTypeParameterList createTypeParameterListWithUsedTypeParameters(@NotNull final PsiElement... elements) {
1319     return createTypeParameterListWithUsedTypeParameters(null, elements);
1320   }
1321
1322   @Nullable
1323   public static PsiTypeParameterList createTypeParameterListWithUsedTypeParameters(@Nullable final PsiTypeParameterList fromList,
1324                                                                                    @NotNull final PsiElement... elements) {
1325     return createTypeParameterListWithUsedTypeParameters(fromList, Conditions.alwaysTrue(), elements);
1326   }
1327
1328   @Nullable
1329   public static PsiTypeParameterList createTypeParameterListWithUsedTypeParameters(@Nullable final PsiTypeParameterList fromList,
1330                                                                                    Condition<? super PsiTypeParameter> filter,
1331                                                                                    @NotNull final PsiElement... elements) {
1332     if (elements.length == 0) return null;
1333     final Set<PsiTypeParameter> used = new HashSet<>();
1334     for (final PsiElement element : elements) {
1335       if (element == null) continue;
1336       collectTypeParameters(used, element, filter);  //pull up extends cls class with type params
1337
1338     }
1339
1340     collectTypeParametersInDependencies(filter, used);
1341
1342     if (fromList != null) {
1343       used.retainAll(Arrays.asList(fromList.getTypeParameters()));
1344     }
1345
1346     PsiTypeParameter[] typeParameters = used.toArray(PsiTypeParameter.EMPTY_ARRAY);
1347
1348     Arrays.sort(typeParameters, Comparator.comparingInt(tp -> tp.getTextRange().getStartOffset()));
1349
1350     final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(elements[0].getProject());
1351     try {
1352       final PsiClass aClass = elementFactory.createClassFromText("class A {}", null);
1353       PsiTypeParameterList list = aClass.getTypeParameterList();
1354       assert list != null;
1355       for (final PsiTypeParameter typeParameter : typeParameters) {
1356         list.add(typeParameter);
1357       }
1358       return list;
1359     }
1360     catch (IncorrectOperationException e) {
1361       LOG.error(e);
1362       assert false;
1363       return null;
1364     }
1365   }
1366
1367   private static void collectTypeParametersInDependencies(Condition<? super PsiTypeParameter> filter, Set<PsiTypeParameter> used) {
1368     Stack<PsiTypeParameter> toProcess = new Stack<>();
1369     toProcess.addAll(used);
1370     while (!toProcess.isEmpty()) {
1371       PsiTypeParameter parameter = toProcess.pop();
1372       HashSet<PsiTypeParameter> dependencies = new HashSet<>();
1373       collectTypeParameters(dependencies, parameter, param -> filter.value(param) && !used.contains(param));
1374       used.addAll(dependencies);
1375       toProcess.addAll(dependencies);
1376     }
1377   }
1378
1379   public static void collectTypeParameters(final Set<? super PsiTypeParameter> used, final PsiElement element) {
1380     collectTypeParameters(used, element, Conditions.alwaysTrue());
1381   }
1382   public static void collectTypeParameters(final Set<? super PsiTypeParameter> used, final PsiElement element,
1383                                            final Condition<? super PsiTypeParameter> filter) {
1384     element.accept(new JavaRecursiveElementVisitor() {
1385       @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
1386         super.visitReferenceElement(reference);
1387         if (!reference.isQualified()) {
1388           final PsiElement resolved = reference.resolve();
1389           if (resolved instanceof PsiTypeParameter) {
1390             final PsiTypeParameter typeParameter = (PsiTypeParameter)resolved;
1391             if (PsiTreeUtil.isAncestor(typeParameter.getOwner(), element, false) && filter.value(typeParameter)) {
1392               used.add(typeParameter);
1393             }
1394           }
1395         }
1396       }
1397
1398       @Override
1399       public void visitExpression(final PsiExpression expression) {
1400         super.visitExpression(expression);
1401         final PsiType type = expression.getType();
1402         if (type != null) {
1403           final PsiTypesUtil.TypeParameterSearcher searcher = new PsiTypesUtil.TypeParameterSearcher();
1404           type.accept(searcher);
1405           for (PsiTypeParameter typeParam : searcher.getTypeParameters()) {
1406             if (PsiTreeUtil.isAncestor(typeParam.getOwner(), element, false) && filter.value(typeParam)){
1407               used.add(typeParam);
1408             }
1409           }
1410         }
1411       }
1412     });
1413   }
1414 }