3 * Copyright 2000-2009 JetBrains s.r.o.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.refactoring.inline;
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;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
55 public class InlineLocalHandler extends JavaInlineActionHandler {
56 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineLocalHandler");
58 private static final String REFACTORING_NAME = RefactoringBundle.message("inline.variable.title");
60 public boolean canInlineElement(PsiElement element) {
61 return element instanceof PsiLocalVariable;
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);
71 * should be called in AtomicAction
73 public static void invoke(@NotNull final Project project, final Editor editor, final PsiLocalVariable local, PsiReferenceExpression refExpr) {
74 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, local)) return;
76 final HighlightManager highlightManager = HighlightManager.getInstance(project);
78 final String localName = local.getName();
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);
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);
101 psiClass = parentPsiClass;
107 final PsiCodeBlock containerBlock = PsiTreeUtil.getParentOfType(local, PsiCodeBlock.class);
108 LOG.assertTrue(containerBlock != null, local);
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);
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);
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);
132 final PsiElement[] refsToInline = PsiUtilBase.toPsiElementArray(refsToInlineList);
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"));
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);
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);
163 if (!isSameDefinition) {
164 highlightManager.addOccurrenceHighlights(editor, defs, writeAttributes, true, null);
165 highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{ref}, attributes, true, null);
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"));
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"));
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(
189 attributes, true, null
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(
199 HelpID.INLINE_VARIABLE,
200 "OptionPane.questionIcon",
205 WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
210 final Runnable runnable = new Runnable() {
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);
219 if (!isInliningVariableInitializer(defToInline)) {
220 defToInline.getParent().delete();
222 defToInline.delete();
225 if (ReferencesSearch.search(local).findFirst() == null) {
226 QuickFixFactory.getInstance().createRemoveUnusedVariableFix(local).invoke(project, editor, local.getContainingFile());
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"));
234 for (final PsiExpression expr : exprs) {
235 InlineUtil.tryToInlineArrayCreationForVarargs(expr);
238 catch (IncorrectOperationException e){
244 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
246 ApplicationManager.getApplication().runWriteAction(runnable);
248 }, RefactoringBundle.message("inline.command", localName), null);
252 public static PsiElement checkRefsInAugmentedAssignmentOrUnaryModified(final PsiElement[] refsToInline) {
253 for (PsiElement element : refsToInline) {
255 PsiElement parent = element.getParent();
256 if (parent instanceof PsiArrayAccessExpression) {
258 parent = parent.getParent();
261 if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression)parent).getLExpression()
262 || isUnaryWriteExpression(parent)) {
270 private static boolean isUnaryWriteExpression(PsiElement parent) {
271 IElementType tokenType = null;
272 if (parent instanceof PsiPrefixExpression) {
273 tokenType = ((PsiPrefixExpression)parent).getOperationTokenType();
275 if (parent instanceof PsiPostfixExpression) {
276 tokenType = ((PsiPostfixExpression)parent).getOperationTokenType();
278 return tokenType == JavaTokenType.PLUSPLUS || tokenType == JavaTokenType.MINUSMINUS;
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());
287 private static boolean isInliningVariableInitializer(final PsiExpression defToInline) {
288 return defToInline.getParent() instanceof PsiVariable;
292 private static PsiExpression getDefToInline(final PsiLocalVariable local,
293 final PsiElement refExpr,
294 final PsiCodeBlock block) {
295 if (refExpr != null) {
297 if (refExpr instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) refExpr)) {
301 final PsiElement[] defs = DefUseUtil.getDefs(block, local, refExpr);
302 if (defs.length == 1) {
310 if (def instanceof PsiReferenceExpression && def.getParent() instanceof PsiAssignmentExpression) {
311 final PsiExpression rExpr = ((PsiAssignmentExpression)def.getParent()).getRExpression();
312 if (rExpr != null) return rExpr;
315 return local.getInitializer();