EA-31572 - assert: InlineLocalHandler.invoke
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineLocalHandler.java
1
2 /*
3  * Copyright 2000-2009 JetBrains s.r.o.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.intellij.refactoring.inline;
18
19 import com.intellij.codeInsight.TargetElementUtilBase;
20 import com.intellij.codeInsight.highlighting.HighlightManager;
21 import com.intellij.codeInsight.intention.QuickFixFactory;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.command.CommandProcessor;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.colors.EditorColors;
27 import com.intellij.openapi.editor.colors.EditorColorsManager;
28 import com.intellij.openapi.editor.markup.TextAttributes;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.wm.WindowManager;
31 import com.intellij.psi.*;
32 import com.intellij.psi.controlFlow.DefUseUtil;
33 import com.intellij.psi.search.GlobalSearchScope;
34 import com.intellij.psi.search.searches.ReferencesSearch;
35 import com.intellij.psi.tree.IElementType;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.psi.util.PsiUtil;
38 import com.intellij.psi.util.PsiUtilBase;
39 import com.intellij.refactoring.HelpID;
40 import com.intellij.refactoring.RefactoringBundle;
41 import com.intellij.refactoring.util.CommonRefactoringUtil;
42 import com.intellij.refactoring.util.InlineUtil;
43 import com.intellij.refactoring.util.RefactoringMessageDialog;
44 import com.intellij.util.ArrayUtil;
45 import com.intellij.util.IncorrectOperationException;
46 import com.intellij.util.Processor;
47 import com.intellij.util.Query;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54
55 public class InlineLocalHandler extends JavaInlineActionHandler {
56   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineLocalHandler");
57
58   private static final String REFACTORING_NAME = RefactoringBundle.message("inline.variable.title");
59
60   public boolean canInlineElement(PsiElement element) {
61     return element instanceof PsiLocalVariable;
62   }
63
64   public void inlineElement(Project project, Editor editor, PsiElement element) {
65     final PsiReference psiReference = TargetElementUtilBase.findReference(editor);
66     final PsiReferenceExpression refExpr = psiReference instanceof PsiReferenceExpression ? ((PsiReferenceExpression)psiReference) : null;
67     invoke(project, editor, (PsiLocalVariable) element, refExpr);
68   }
69
70   /**
71    * should be called in AtomicAction
72    */
73   public static void invoke(@NotNull final Project project, final Editor editor, final PsiLocalVariable local, PsiReferenceExpression refExpr) {
74     if (!CommonRefactoringUtil.checkReadOnlyStatus(project, local)) return;
75
76     final HighlightManager highlightManager = HighlightManager.getInstance(project);
77
78     final String localName = local.getName();
79
80     final Query<PsiReference> query = ReferencesSearch.search(local, GlobalSearchScope.allScope(project), false);
81     if (query.findFirst() == null){
82       LOG.assertTrue(refExpr == null);
83       String message = RefactoringBundle.message("variable.is.never.used", localName);
84       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
85       return;
86     }
87
88     final PsiClass containingClass = PsiTreeUtil.getParentOfType(local, PsiClass.class);
89     final List<PsiClass> innerClassesWithUsages = Collections.synchronizedList(new ArrayList<PsiClass>());
90     final List<PsiElement> innerClassUsages = Collections.synchronizedList(new ArrayList<PsiElement>());
91     query.forEach(new Processor<PsiReference>() {
92       public boolean process(final PsiReference psiReference) {
93         final PsiElement element = psiReference.getElement();
94         PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
95         while (psiClass != containingClass && psiClass != null) {
96           final PsiClass parentPsiClass = PsiTreeUtil.getParentOfType(psiClass, PsiClass.class, true);
97           if (parentPsiClass == containingClass) {
98             innerClassesWithUsages.add(psiClass);
99             innerClassUsages.add(element);
100           }
101           psiClass = parentPsiClass;
102         }
103         return true;
104       }
105     });
106
107     final PsiCodeBlock containerBlock = PsiTreeUtil.getParentOfType(local, PsiCodeBlock.class);
108     LOG.assertTrue(containerBlock != null, local);
109
110     final PsiExpression defToInline = innerClassesWithUsages.isEmpty()
111                                       ? getDefToInline(local, refExpr, containerBlock)
112                                       : getDefToInline(local, innerClassesWithUsages.get(0), containerBlock);
113     if (defToInline == null){
114       final String key = refExpr == null ? "variable.has.no.initializer" : "variable.has.no.dominating.definition";
115       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, localName));
116       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
117       return;
118     }
119
120     final List<PsiElement> refsToInlineList = new ArrayList<PsiElement>();
121     Collections.addAll(refsToInlineList, DefUseUtil.getRefs(containerBlock, local, defToInline));
122     for (PsiElement innerClassUsage : innerClassUsages) {
123       if (!refsToInlineList.contains(innerClassUsage)) {
124         refsToInlineList.add(innerClassUsage);
125       }
126     }
127     if (refsToInlineList.size() == 0) {
128       String message = RefactoringBundle.message("variable.is.never.used.before.modification", localName);
129       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
130       return;
131     }
132     final PsiElement[] refsToInline = PsiUtilBase.toPsiElementArray(refsToInlineList);
133
134     EditorColorsManager manager = EditorColorsManager.getInstance();
135     final TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
136     final TextAttributes writeAttributes = manager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES);
137     if (refExpr != null && PsiUtil.isAccessedForReading(refExpr) && ArrayUtil.find(refsToInline, refExpr) < 0) {
138       final PsiElement[] defs = DefUseUtil.getDefs(containerBlock, local, refExpr);
139       LOG.assertTrue(defs.length > 0);
140       highlightManager.addOccurrenceHighlights(editor, defs, attributes, true, null);
141       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing", localName));
142       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
143       WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
144       return;
145     }
146
147     PsiFile workingFile = local.getContainingFile();
148     for (PsiElement ref : refsToInline) {
149       final PsiFile otherFile = ref.getContainingFile();
150       if (!otherFile.equals(workingFile)) {
151         String message = RefactoringBundle.message("variable.is.referenced.in.multiple.files", localName);
152         CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
153         return;
154       }
155     }
156
157     for (final PsiElement ref : refsToInline) {
158       final PsiElement[] defs = DefUseUtil.getDefs(containerBlock, local, ref);
159       boolean isSameDefinition = true;
160       for (PsiElement def : defs) {
161         isSameDefinition &= isSameDefinition(def, defToInline);
162       }
163       if (!isSameDefinition) {
164         highlightManager.addOccurrenceHighlights(editor, defs, writeAttributes, true, null);
165         highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{ref}, attributes, true, null);
166         String message =
167           RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing.and.used.with.inlined", localName));
168         CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
169         WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
170         return;
171       }
172     }
173
174     final PsiElement writeAccess = checkRefsInAugmentedAssignmentOrUnaryModified(refsToInline);
175     if (writeAccess != null) {
176       HighlightManager.getInstance(project).addOccurrenceHighlights(editor, new PsiElement[]{writeAccess}, writeAttributes, true, null);
177       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing", localName));
178       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE);
179       WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
180       return;
181     }
182
183     if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) {
184       // TODO : check if initializer uses fieldNames that possibly will be hidden by other
185       // locals with the same names after inlining
186       highlightManager.addOccurrenceHighlights(
187         editor,
188         refsToInline,
189         attributes, true, null
190       );
191       int occurrencesCount = refsToInline.length;
192       String occurencesString = RefactoringBundle.message("occurences.string", occurrencesCount);
193       final String promptKey = isInliningVariableInitializer(defToInline)
194                                ? "inline.local.variable.prompt" : "inline.local.variable.definition.prompt";
195       final String question = RefactoringBundle.message(promptKey, localName) + " " + occurencesString;
196       RefactoringMessageDialog dialog = new RefactoringMessageDialog(
197         REFACTORING_NAME,
198         question,
199         HelpID.INLINE_VARIABLE,
200         "OptionPane.questionIcon",
201         true,
202         project);
203       dialog.show();
204       if (!dialog.isOK()){
205         WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
206         return;
207       }
208     }
209
210     final Runnable runnable = new Runnable() {
211       public void run() {
212         try{
213           PsiExpression[] exprs = new PsiExpression[refsToInline.length];
214           for(int idx = 0; idx < refsToInline.length; idx++){
215             PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement)refsToInline[idx];
216             exprs[idx] = InlineUtil.inlineVariable(local, defToInline, refElement);
217           }
218
219           if (!isInliningVariableInitializer(defToInline)) {
220             defToInline.getParent().delete();
221           } else {
222             defToInline.delete();
223           }
224
225           if (ReferencesSearch.search(local).findFirst() == null) {
226             QuickFixFactory.getInstance().createRemoveUnusedVariableFix(local).invoke(project, editor, local.getContainingFile());
227           }
228
229           if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) {
230             highlightManager.addOccurrenceHighlights(editor, exprs, attributes, true, null);
231             WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
232           }
233
234           for (final PsiExpression expr : exprs) {
235             InlineUtil.tryToInlineArrayCreationForVarargs(expr);
236           }
237         }
238         catch (IncorrectOperationException e){
239           LOG.error(e);
240         }
241       }
242     };
243
244     CommandProcessor.getInstance().executeCommand(project, new Runnable() {
245       public void run() {
246         ApplicationManager.getApplication().runWriteAction(runnable);
247       }
248     }, RefactoringBundle.message("inline.command", localName), null);
249   }
250
251   @Nullable
252   public static PsiElement checkRefsInAugmentedAssignmentOrUnaryModified(final PsiElement[] refsToInline) {
253     for (PsiElement element : refsToInline) {
254
255       PsiElement parent = element.getParent();
256       if (parent instanceof PsiArrayAccessExpression) {
257         element = parent;
258         parent = parent.getParent();
259       }
260
261       if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression)parent).getLExpression()
262           || isUnaryWriteExpression(parent)) {
263
264         return element;
265       }
266     }
267     return null;
268   }
269
270   private static boolean isUnaryWriteExpression(PsiElement parent) {
271     IElementType tokenType = null;
272     if (parent instanceof PsiPrefixExpression) {
273       tokenType = ((PsiPrefixExpression)parent).getOperationTokenType();
274     }
275     if (parent instanceof PsiPostfixExpression) {
276       tokenType = ((PsiPostfixExpression)parent).getOperationTokenType();
277     }
278     return tokenType == JavaTokenType.PLUSPLUS || tokenType == JavaTokenType.MINUSMINUS;
279   }
280
281   private static boolean isSameDefinition(final PsiElement def, final PsiExpression defToInline) {
282     if (def instanceof PsiLocalVariable) return defToInline.equals(((PsiLocalVariable)def).getInitializer());
283     final PsiElement parent = def.getParent();
284     return parent instanceof PsiAssignmentExpression && defToInline.equals(((PsiAssignmentExpression)parent).getRExpression());
285   }
286
287   private static boolean isInliningVariableInitializer(final PsiExpression defToInline) {
288     return defToInline.getParent() instanceof PsiVariable;
289   }
290
291   @Nullable
292   private static PsiExpression getDefToInline(final PsiLocalVariable local,
293                                               final PsiElement refExpr,
294                                               final PsiCodeBlock block) {
295     if (refExpr != null) {
296       PsiElement def;
297       if (refExpr instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) refExpr)) {
298         def = refExpr;
299       }
300       else {
301         final PsiElement[] defs = DefUseUtil.getDefs(block, local, refExpr);
302         if (defs.length == 1) {
303           def = defs[0];
304         }
305         else {
306           return null;
307         }
308       }
309
310       if (def instanceof PsiReferenceExpression && def.getParent() instanceof PsiAssignmentExpression) {
311         final PsiExpression rExpr = ((PsiAssignmentExpression)def.getParent()).getRExpression();
312         if (rExpr != null) return rExpr;
313       }
314     }
315     return local.getInitializer();
316   }
317 }