introduce variable on non-expressive line ( IDEA-26367 )
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / introduceVariable / IntroduceVariableBase.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * Created by IntelliJ IDEA.
19  * User: dsl
20  * Date: Nov 15, 2002
21  * Time: 5:21:33 PM
22  * To change this template use Options | File Templates.
23  */
24 package com.intellij.refactoring.introduceVariable;
25
26 import com.intellij.codeInsight.CodeInsightUtil;
27 import com.intellij.codeInsight.unwrap.ScopeHighlighter;
28 import com.intellij.featureStatistics.FeatureUsageTracker;
29 import com.intellij.ide.util.PropertiesComponent;
30 import com.intellij.openapi.actionSystem.DataContext;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.command.CommandProcessor;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.*;
35 import com.intellij.openapi.fileEditor.FileDocumentManager;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.ui.popup.JBPopupAdapter;
38 import com.intellij.openapi.ui.popup.JBPopupFactory;
39 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
40 import com.intellij.openapi.util.Pass;
41 import com.intellij.openapi.util.TextRange;
42 import com.intellij.openapi.util.text.StringUtil;
43 import com.intellij.psi.*;
44 import com.intellij.psi.codeStyle.CodeStyleManager;
45 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
46 import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil;
47 import com.intellij.psi.util.PsiTreeUtil;
48 import com.intellij.psi.util.PsiUtil;
49 import com.intellij.refactoring.IntroduceHandlerBase;
50 import com.intellij.refactoring.RefactoringActionHandler;
51 import com.intellij.refactoring.RefactoringBundle;
52 import com.intellij.refactoring.introduceField.ElementToWorkOn;
53 import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
54 import com.intellij.refactoring.util.CommonRefactoringUtil;
55 import com.intellij.refactoring.util.FieldConflictsResolver;
56 import com.intellij.refactoring.util.RefactoringUIUtil;
57 import com.intellij.refactoring.util.RefactoringUtil;
58 import com.intellij.refactoring.util.occurences.ExpressionOccurenceManager;
59 import com.intellij.refactoring.util.occurences.NotInSuperCallOccurenceFilter;
60 import com.intellij.util.IncorrectOperationException;
61 import com.intellij.util.containers.MultiMap;
62 import org.jetbrains.annotations.NonNls;
63 import org.jetbrains.annotations.NotNull;
64 import org.jetbrains.annotations.Nullable;
65
66 import javax.swing.*;
67 import javax.swing.event.ListSelectionEvent;
68 import javax.swing.event.ListSelectionListener;
69 import java.awt.*;
70 import java.util.ArrayList;
71 import java.util.List;
72
73 public abstract class IntroduceVariableBase extends IntroduceHandlerBase implements RefactoringActionHandler {
74   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase");
75   @NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements";
76
77   protected static String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title");
78
79   public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) {
80     final SelectionModel selectionModel = editor.getSelectionModel();
81     if (!selectionModel.hasSelection()) {
82       final int offset = editor.getCaretModel().getOffset();
83       final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset);
84
85       //try line selection
86       if (statementsInRange.length == 1 && (PsiUtil.hasErrorElementChild(statementsInRange[0]) || isPreferStatements())) {
87         selectionModel.selectLineAtCaret();
88         if (findExpressionInRange(project, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) == null) {
89           selectionModel.removeSelection();
90         }
91       }
92
93       if (!selectionModel.hasSelection()) {
94         final List<PsiExpression> expressions = collectExpressions(file, editor, offset, statementsInRange);
95         if (expressions.isEmpty()) {
96           selectionModel.selectLineAtCaret();
97         } else if (expressions.size() == 1) {
98           final TextRange textRange = expressions.get(0).getTextRange();
99           selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
100         }
101         else {
102           showChooser(editor, expressions, new Pass<PsiExpression>(){
103             public void pass(final PsiExpression selectedValue) {
104               invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset());
105             }
106           });
107           return;
108         }
109       }
110     }
111     if (invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd())) {
112       selectionModel.removeSelection();
113     }
114   }
115
116   public static boolean isPreferStatements() {
117     return Boolean.valueOf(PropertiesComponent.getInstance().getOrInit(PREFER_STATEMENTS_OPTION, "false")).booleanValue();
118   }
119
120   public static List<PsiExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset, final PsiElement... statementsInRange) {
121     Document document = editor.getDocument();
122     CharSequence text = document.getCharsSequence();
123     int correctedOffset = offset;
124     int textLength = document.getTextLength();
125     if (offset >= textLength) {
126       correctedOffset = textLength - 1;
127     }
128     else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
129       correctedOffset--;
130     }
131     if (correctedOffset < 0) {
132       correctedOffset = offset;
133     }
134     else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) {
135       if (text.charAt(correctedOffset) != ')') {
136         correctedOffset = offset;
137       }
138     }
139     final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
140     final List<PsiExpression> expressions = new ArrayList<PsiExpression>();
141     /*for (PsiElement element : statementsInRange) {
142       if (element instanceof PsiExpressionStatement) {
143         final PsiExpression expression = ((PsiExpressionStatement)element).getExpression();
144         if (expression.getType() != PsiType.VOID) {
145           expressions.add(expression);
146         }
147       }
148     }*/
149     PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class);
150     while (expression != null) {
151       if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) && expression.getType() != PsiType.VOID) {
152         if (!(expression instanceof PsiReferenceExpression && (expression.getParent() instanceof PsiMethodCallExpression ||
153                                                                ((PsiReferenceExpression)expression).resolve() instanceof PsiClass))
154             && !(expression instanceof PsiAssignmentExpression)) {
155           expressions.add(expression);
156         }
157       }
158       expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class);
159     }
160     return expressions;
161   }
162
163   public static PsiElement[] findStatementsAtOffset(final Editor editor, final PsiFile file, final int offset) {
164     final Document document = editor.getDocument();
165     final int lineNumber = document.getLineNumber(offset);
166     final int lineStart = document.getLineStartOffset(lineNumber);
167     final int lineEnd = document.getLineEndOffset(lineNumber);
168
169     return CodeInsightUtil.findStatementsInRange(file, lineStart, lineEnd);
170   }
171
172   public static void showChooser(final Editor editor, final List<PsiExpression> expressions, final Pass<PsiExpression> callback) {
173     final ScopeHighlighter highlighter = new ScopeHighlighter(editor);
174     final DefaultListModel model = new DefaultListModel();
175     for (PsiExpression expr : expressions) {
176       model.addElement(expr);
177     }
178     final JList list = new JList(model);
179     list.setCellRenderer(new DefaultListCellRenderer() {
180
181       @Override
182       public Component getListCellRendererComponent(final JList list,
183                                                     final Object value,
184                                                     final int index,
185                                                     final boolean isSelected,
186                                                     final boolean cellHasFocus) {
187         final Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
188
189         final StringBuffer buf = new StringBuffer();
190         final PsiExpression expr = (PsiExpression)value;
191         if (expr.isValid()) {
192           expr.accept(new PsiExpressionTrimRenderer(buf));
193           setText(buf.toString());
194         }
195         return rendererComponent;
196       }
197     });
198
199     list.addListSelectionListener(new ListSelectionListener() {
200       public void valueChanged(final ListSelectionEvent e) {
201         highlighter.dropHighlight();
202         final int index = list.getSelectedIndex();
203         if (index < 0 ) return;
204         final PsiExpression expr = (PsiExpression)model.get(index);
205         final ArrayList<PsiElement> toExtract = new ArrayList<PsiElement>();
206         toExtract.add(expr);
207         highlighter.highlight(expr, toExtract);
208       }
209     });
210
211     JBPopupFactory.getInstance().createListPopupBuilder(list)
212           .setTitle("Expressions")
213           .setMovable(false)
214           .setResizable(false)
215           .setRequestFocus(true)
216           .setItemChoosenCallback(new Runnable() {
217                                     public void run() {
218                                       callback.pass((PsiExpression)list.getSelectedValue());
219                                     }
220                                   })
221           .addListener(new JBPopupAdapter() {
222                           @Override
223                           public void onClosed(LightweightWindowEvent event) {
224                             highlighter.dropHighlight();
225                           }
226                        })
227           .createPopup().showInBestPositionFor(editor);
228   }
229
230   private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) {
231     FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable");
232     PsiDocumentManager.getInstance(project).commitAllDocuments();
233
234
235     return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor);
236   }
237
238   private static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) {
239     PsiExpression tempExpr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset);
240     if (tempExpr == null) {
241       PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset);
242       if (statements.length == 1) {
243         if (statements[0] instanceof PsiExpressionStatement) {
244           tempExpr = ((PsiExpressionStatement) statements[0]).getExpression();
245         } else if (statements[0] instanceof PsiReturnStatement) {
246           tempExpr = ((PsiReturnStatement)statements[0]).getReturnValue();
247         }
248       }
249     }
250
251     if (tempExpr == null) {
252       tempExpr = getSelectedExpression(project, file, startOffset, endOffset);
253     }
254     return tempExpr;
255   }
256
257   public static PsiExpression getSelectedExpression(final Project project, final PsiFile file, final int startOffset, final int endOffset) {
258
259     final PsiElement elementAtStart = file.findElementAt(startOffset);
260     if (elementAtStart == null) return null;
261     final PsiElement elementAtEnd = file.findElementAt(endOffset - 1);
262     if (elementAtEnd == null) return null;
263
264     PsiExpression tempExpr;
265     final PsiElement elementAt = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd);
266     if (PsiTreeUtil.getParentOfType(elementAt, PsiExpression.class, false) == null) return null;
267     final PsiLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(elementAt, PsiLiteralExpression.class);
268
269     final PsiLiteralExpression startLiteralExpression = PsiTreeUtil.getParentOfType(elementAtStart, PsiLiteralExpression.class);
270     final PsiLiteralExpression endLiteralExpression = PsiTreeUtil.getParentOfType(file.findElementAt(endOffset), PsiLiteralExpression.class);
271
272     final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
273     try {
274       String text = file.getText().subSequence(startOffset, endOffset).toString();
275       String prefix = null;
276       String suffix = null;
277       String stripped = text;
278       if (startLiteralExpression != null) {
279         final int startExpressionOffset = startLiteralExpression.getTextOffset();
280         if (startOffset == startExpressionOffset) {
281           if (StringUtil.startsWithChar(text, '\"') || StringUtil.startsWithChar(text, '\'')) {
282             stripped = text.substring(1);
283           }
284         } else if (startOffset == startExpressionOffset + 1) {
285           text = "\"" + text;
286         } else if (startOffset > startExpressionOffset + 1){
287           prefix = "\" + ";
288           text = "\"" + text;
289         }
290       }
291
292       if (endLiteralExpression != null) {
293         final int endExpressionOffset = endLiteralExpression.getTextOffset() + endLiteralExpression.getTextLength();
294         if (endOffset == endExpressionOffset ) {
295           if (StringUtil.endsWithChar(stripped, '\"') || StringUtil.endsWithChar(stripped, '\'')) {
296             stripped = stripped.substring(0, stripped.length() - 1);
297           }
298         } else if (endOffset == endExpressionOffset - 1) {
299           text += "\"";
300         } else if (endOffset < endExpressionOffset - 1) {
301           suffix = " + \"";
302           text += "\"";
303         }
304       }
305
306       boolean primitive = false;
307       if (stripped.equals("true") || stripped.equals("false")) {
308         primitive = true;
309       }
310       else {
311         try {
312           Integer.parseInt(stripped);
313           primitive = true;
314         }
315         catch (NumberFormatException e1) {
316           //then not primitive
317         }
318       }
319
320       if (primitive) {
321         text = stripped;
322       }
323
324       final PsiElement parent = literalExpression != null ? literalExpression : elementAt;
325       tempExpr = elementFactory.createExpressionFromText(text, parent);
326
327       final boolean [] hasErrors = new boolean[1];
328       final JavaRecursiveElementWalkingVisitor errorsVisitor = new JavaRecursiveElementWalkingVisitor() {
329         @Override
330         public void visitElement(final PsiElement element) {
331           if (hasErrors[0]) {
332             return;
333           }
334           super.visitElement(element);
335         }
336
337         @Override
338         public void visitErrorElement(final PsiErrorElement element) {
339           hasErrors[0] = true;
340         }
341       };
342       tempExpr.accept(errorsVisitor);
343       if (hasErrors[0]) return null;
344
345       tempExpr.putUserData(ElementToWorkOn.PREFIX, prefix);
346       tempExpr.putUserData(ElementToWorkOn.SUFFIX, suffix);
347
348       final RangeMarker rangeMarker =
349         FileDocumentManager.getInstance().getDocument(file.getVirtualFile()).createRangeMarker(startOffset, endOffset);
350       tempExpr.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker);
351
352       tempExpr.putUserData(ElementToWorkOn.PARENT, parent);
353
354       final String fakeInitializer = "intellijidearulezzz";
355       final int[] refIdx = new int[1];
356       final PsiExpression toBeExpression = createReplacement(fakeInitializer, project, prefix, suffix, parent, rangeMarker, refIdx);
357       toBeExpression.accept(errorsVisitor);
358       if (hasErrors[0]) return null;
359
360       final PsiReferenceExpression refExpr = PsiTreeUtil.getParentOfType(toBeExpression.findElementAt(refIdx[0]), PsiReferenceExpression.class);
361       assert refExpr != null;
362       if (ReplaceExpressionUtil.isNeedParenthesis(refExpr.getNode(), tempExpr.getNode())) {
363         return null;
364       }
365     }
366     catch (IncorrectOperationException e) {
367       return null;
368     }
369
370     return tempExpr;
371   }
372
373   protected boolean invokeImpl(final Project project, final PsiExpression expr,
374                                final Editor editor) {
375     if (expr != null && expr.getParent() instanceof PsiExpressionStatement) {
376       FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement");
377     }
378     if (LOG.isDebugEnabled()) {
379       LOG.debug("expression:" + expr);
380     }
381
382     if (expr == null) {
383       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression"));
384       showErrorMessage(project, editor, message);
385       return false;
386     }
387
388     final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
389
390
391     PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expr);
392     if (originalType == null) {
393       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type"));
394       showErrorMessage(project, editor, message);
395       return false;
396     }
397
398     if (PsiType.VOID.equals(originalType)) {
399       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type"));
400       showErrorMessage(project, editor, message);
401       return false;
402     }
403
404
405     final PsiElement physicalElement = expr.getUserData(ElementToWorkOn.PARENT);
406
407     PsiElement anchorStatement = RefactoringUtil.getParentStatement(physicalElement != null ? physicalElement : expr, false);
408
409     if (anchorStatement == null) {
410       return parentStatementNotFound(project, editor);
411     }
412     if (anchorStatement instanceof PsiExpressionStatement) {
413       PsiExpression enclosingExpr = ((PsiExpressionStatement)anchorStatement).getExpression();
414       if (enclosingExpr instanceof PsiMethodCallExpression) {
415         PsiMethod method = ((PsiMethodCallExpression)enclosingExpr).resolveMethod();
416         if (method != null && method.isConstructor()) {
417           //This is either 'this' or 'super', both must be the first in the respective contructor
418           String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("invalid.expression.context"));
419           showErrorMessage(project, editor, message);
420           return false;
421         }
422       }
423     }
424
425     PsiElement tempContainer = anchorStatement.getParent();
426
427     if (!(tempContainer instanceof PsiCodeBlock) && !isLoopOrIf(tempContainer)) {
428       String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME);
429       showErrorMessage(project, editor, message);
430       return false;
431     }
432
433     if(!NotInSuperCallOccurenceFilter.INSTANCE.isOK(expr)) {
434       String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.introduce.variable.in.super.constructor.call"));
435       showErrorMessage(project, editor, message);
436       return false;
437     }
438
439     final PsiFile file = anchorStatement.getContainingFile();
440     LOG.assertTrue(file != null, "expr.getContainingFile() == null");
441
442     if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return false;
443
444     PsiElement containerParent = tempContainer;
445     PsiElement lastScope = tempContainer;
446     while (true) {
447       if (containerParent instanceof PsiFile) break;
448       if (containerParent instanceof PsiMethod) break;
449       containerParent = containerParent.getParent();
450       if (containerParent instanceof PsiCodeBlock) {
451         lastScope = containerParent;
452       }
453     }
454
455     ExpressionOccurenceManager occurenceManager = new ExpressionOccurenceManager(expr, lastScope,
456                                                                                  NotInSuperCallOccurenceFilter.INSTANCE);
457     final PsiExpression[] occurrences = occurenceManager.getOccurences();
458     final PsiElement anchorStatementIfAll = occurenceManager.getAnchorStatementForAll();
459     boolean declareFinalIfAll = occurenceManager.isInFinalContext();
460
461
462     boolean anyAssignmentLHS = false;
463     for (PsiExpression occurrence : occurrences) {
464       if (RefactoringUtil.isAssignmentLHS(occurrence)) {
465         anyAssignmentLHS = true;
466         break;
467       }
468     }
469
470
471     IntroduceVariableSettings settings = getSettings(project, editor, expr, occurrences, anyAssignmentLHS, declareFinalIfAll,
472                                                      originalType,
473                                                      new TypeSelectorManagerImpl(project, originalType, expr, occurrences),
474                                                      new InputValidator(this, project, anchorStatementIfAll, anchorStatement, occurenceManager));
475
476     if (!settings.isOK()) {
477       return false;
478     }
479
480     final String variableName = settings.getEnteredName();
481
482     final PsiType type = settings.getSelectedType();
483     final boolean replaceAll = settings.isReplaceAllOccurrences();
484     final boolean replaceWrite = settings.isReplaceLValues();
485     final boolean declareFinal = replaceAll && declareFinalIfAll || settings.isDeclareFinal();
486     if (replaceAll) {
487       anchorStatement = anchorStatementIfAll;
488       tempContainer = anchorStatement.getParent();
489     }
490
491     final PsiElement container = tempContainer;
492
493     PsiElement child = anchorStatement;
494     if (!isLoopOrIf(container)) {
495       child = locateAnchor(child);
496     }
497     final PsiElement anchor = child == null ? anchorStatement : child;
498
499     boolean tempDeleteSelf = false;
500     final boolean replaceSelf = replaceWrite || !RefactoringUtil.isAssignmentLHS(expr);
501     if (!isLoopOrIf(container)) {
502       if (expr.getParent() instanceof PsiExpressionStatement && anchor.equals(anchorStatement)) {
503         PsiStatement statement = (PsiStatement) expr.getParent();
504         PsiElement parent = statement.getParent();
505         if (parent instanceof PsiCodeBlock ||
506             //fabrique
507             parent instanceof PsiCodeFragment) {
508           tempDeleteSelf = true;
509         }
510       }
511       tempDeleteSelf &= replaceSelf;
512     }
513     final boolean deleteSelf = tempDeleteSelf;
514
515
516     final int col = editor != null ? editor.getCaretModel().getLogicalPosition().column : 0;
517     final int line = editor != null ? editor.getCaretModel().getLogicalPosition().line : 0;
518     if (deleteSelf) {
519       if (editor != null) {
520         LogicalPosition pos = new LogicalPosition(line, col);
521         editor.getCaretModel().moveToLogicalPosition(pos);
522       }
523     }
524
525     final PsiCodeBlock newDeclarationScope = PsiTreeUtil.getParentOfType(container, PsiCodeBlock.class, false);
526     final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(variableName, newDeclarationScope);
527
528     final PsiElement finalAnchorStatement = anchorStatement;
529     final Runnable runnable = new Runnable() {
530       public void run() {
531         try {
532           PsiStatement statement = null;
533           final boolean isInsideLoop = isLoopOrIf(container);
534           if (!isInsideLoop && deleteSelf) {
535             statement = (PsiStatement) expr.getParent();
536           }
537           final PsiExpression expr1 = fieldConflictsResolver.fixInitializer(expr);
538           PsiExpression initializer = RefactoringUtil.unparenthesizeExpression(expr1);
539           if (expr1 instanceof PsiNewExpression) {
540             final PsiNewExpression newExpression = (PsiNewExpression)expr1;
541             if (newExpression.getArrayInitializer() != null) {
542               initializer = newExpression.getArrayInitializer();
543             }
544           }
545           PsiDeclarationStatement declaration = factory.createVariableDeclarationStatement(variableName, type, initializer);
546           if (!isInsideLoop) {
547             declaration = (PsiDeclarationStatement) container.addBefore(declaration, anchor);
548             LOG.assertTrue(expr1.isValid());
549             if (deleteSelf) { // never true
550               final PsiElement lastChild = statement.getLastChild();
551               if (lastChild instanceof PsiComment) { // keep trailing comment
552                 declaration.addBefore(lastChild, null);
553               }
554               statement.delete();
555               if (editor != null) {
556                 LogicalPosition pos = new LogicalPosition(line, col);
557                 editor.getCaretModel().moveToLogicalPosition(pos);
558                 editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset());
559                 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
560                 editor.getSelectionModel().removeSelection();
561               }
562             }
563           }
564
565           PsiExpression ref = factory.createExpressionFromText(variableName, null);
566           if (replaceAll) {
567             ArrayList<PsiElement> array = new ArrayList<PsiElement>();
568             for (PsiExpression occurrence : occurrences) {
569               if (deleteSelf && occurrence.equals(expr)) continue;
570               if (occurrence.equals(expr)) {
571                 occurrence = expr1;
572               }
573               if (occurrence != null) {
574                 occurrence = RefactoringUtil.outermostParenthesizedExpression(occurrence);
575               }
576               if (replaceWrite || !RefactoringUtil.isAssignmentLHS(occurrence)) {
577                 array.add(replace(occurrence, ref, project));
578               }
579             }
580
581             if (editor != null) {
582               final PsiElement[] replacedOccurences = array.toArray(new PsiElement[array.size()]);
583               highlightReplacedOccurences(project, editor, replacedOccurences);
584             }
585           } else {
586             if (!deleteSelf && replaceSelf) {
587               replace(expr1, ref, project);
588             }
589           }
590
591           declaration = (PsiDeclarationStatement) putStatementInLoopBody(declaration, container, finalAnchorStatement);
592           declaration = (PsiDeclarationStatement)JavaCodeStyleManager.getInstance(project).shortenClassReferences(declaration);
593           PsiVariable var = (PsiVariable) declaration.getDeclaredElements()[0];
594           PsiUtil.setModifierProperty(var, PsiModifier.FINAL, declareFinal);
595
596           fieldConflictsResolver.fix();
597         } catch (IncorrectOperationException e) {
598           LOG.error(e);
599         }
600       }
601     };
602
603     CommandProcessor.getInstance().executeCommand(
604       project,
605       new Runnable() {
606         public void run() {
607           ApplicationManager.getApplication().runWriteAction(runnable);
608         }
609       }, REFACTORING_NAME, null);
610     return true;
611   }
612
613   public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project)
614     throws IncorrectOperationException {
615     final PsiExpression expr2 = RefactoringUtil.outermostParenthesizedExpression(expr1);
616     if (expr2.isPhysical()) {
617       return expr2.replace(ref);
618     }
619     else {
620       final String prefix  = expr1.getUserData(ElementToWorkOn.PREFIX);
621       final String suffix  = expr1.getUserData(ElementToWorkOn.SUFFIX);
622       final PsiElement parent = expr1.getUserData(ElementToWorkOn.PARENT);
623       final RangeMarker rangeMarker = expr1.getUserData(ElementToWorkOn.TEXT_RANGE);
624
625       return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, rangeMarker, new int[1]));
626     }
627   }
628
629   private static PsiExpression createReplacement(final String refText, final Project project,
630                                                  final String prefix,
631                                                  final String suffix,
632                                                  final PsiElement parent, final RangeMarker rangeMarker, int[] refIdx) {
633     final String allText = parent.getContainingFile().getText();
634     final TextRange parentRange = parent.getTextRange();
635
636     String beg = allText.substring(parentRange.getStartOffset(), rangeMarker.getStartOffset());
637     if (StringUtil.stripQuotesAroundValue(beg).trim().length() == 0 && prefix == null) beg = "";
638
639     String end = allText.substring(rangeMarker.getEndOffset(), parentRange.getEndOffset());
640     if (StringUtil.stripQuotesAroundValue(end).trim().length() == 0 && suffix == null) end = "";
641
642     final String start = beg + (prefix != null ? prefix : "");
643     refIdx[0] = start.length();
644     final String text = start + refText + (suffix != null ? suffix : "") + end;
645     return JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(text, parent);
646   }
647
648   public static PsiStatement putStatementInLoopBody(PsiStatement declaration, PsiElement container, PsiElement finalAnchorStatement)
649     throws IncorrectOperationException {
650     if(isLoopOrIf(container)) {
651       PsiStatement loopBody = getLoopBody(container, finalAnchorStatement);
652       PsiStatement loopBodyCopy = loopBody != null ? (PsiStatement) loopBody.copy() : null;
653       PsiBlockStatement blockStatement = (PsiBlockStatement)JavaPsiFacade.getInstance(container.getProject()).getElementFactory()
654         .createStatementFromText("{}", null);
655       blockStatement = (PsiBlockStatement) CodeStyleManager.getInstance(container.getProject()).reformat(blockStatement);
656       final PsiElement prevSibling = loopBody.getPrevSibling();
657       if(prevSibling instanceof PsiWhiteSpace) {
658         final PsiElement pprev = prevSibling.getPrevSibling();
659         if (!(pprev instanceof PsiComment) || !((PsiComment)pprev).getTokenType().equals(JavaTokenType.END_OF_LINE_COMMENT)) {
660           prevSibling.delete();
661         }
662       }
663       blockStatement = (PsiBlockStatement) loopBody.replace(blockStatement);
664       final PsiCodeBlock codeBlock = blockStatement.getCodeBlock();
665       declaration = (PsiStatement) codeBlock.add(declaration);
666       JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration);
667       if (loopBodyCopy != null) codeBlock.add(loopBodyCopy);
668     }
669     return declaration;
670   }
671
672   private boolean parentStatementNotFound(final Project project, Editor editor) {
673     String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME);
674     showErrorMessage(project, editor, message);
675     return false;
676   }
677
678   protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) {
679     throw new UnsupportedOperationException();
680   }
681
682   private static PsiElement locateAnchor(PsiElement child) {
683     while (child != null) {
684       PsiElement prev = child.getPrevSibling();
685       if (prev instanceof PsiStatement) break;
686       if (prev instanceof PsiJavaToken && ((PsiJavaToken)prev).getTokenType() == JavaTokenType.LBRACE) break;
687       child = prev;
688     }
689
690     while (child instanceof PsiWhiteSpace || child instanceof PsiComment) {
691       child = child.getNextSibling();
692     }
693     return child;
694   }
695
696   protected abstract void highlightReplacedOccurences(Project project, Editor editor, PsiElement[] replacedOccurences);
697
698   protected abstract IntroduceVariableSettings getSettings(Project project, Editor editor, PsiExpression expr, final PsiElement[] occurrences,
699                                                            boolean anyAssignmentLHS, final boolean declareFinalIfAll, final PsiType type,
700                                                            TypeSelectorManagerImpl typeSelectorManager, InputValidator validator);
701
702   protected abstract void showErrorMessage(Project project, Editor editor, String message);
703
704   @Nullable
705   private static PsiStatement getLoopBody(PsiElement container, PsiElement anchorStatement) {
706     if(container instanceof PsiLoopStatement) {
707       return ((PsiLoopStatement) container).getBody();
708     }
709     else if (container instanceof PsiIfStatement) {
710       final PsiStatement thenBranch = ((PsiIfStatement)container).getThenBranch();
711       if (thenBranch != null && PsiTreeUtil.isAncestor(thenBranch, anchorStatement, false)) {
712         return thenBranch;
713       }
714       final PsiStatement elseBranch = ((PsiIfStatement)container).getElseBranch();
715       if (elseBranch != null && PsiTreeUtil.isAncestor(elseBranch, anchorStatement, false)) {
716         return elseBranch;
717       }
718       LOG.assertTrue(false);
719     }
720     LOG.assertTrue(false);
721     return null;
722   }
723
724
725   public static boolean isLoopOrIf(PsiElement element) {
726     return element instanceof PsiLoopStatement || element instanceof PsiIfStatement;
727   }
728
729   public interface Validator {
730     boolean isOK(IntroduceVariableSettings dialog);
731   }
732
733   protected abstract boolean reportConflicts(MultiMap<PsiElement,String> conflicts, final Project project, IntroduceVariableSettings dialog);
734
735
736   public static void checkInLoopCondition(PsiExpression occurence, MultiMap<PsiElement, String> conflicts) {
737     final PsiElement loopForLoopCondition = RefactoringUtil.getLoopForLoopCondition(occurence);
738     if (loopForLoopCondition == null) return;
739     final List<PsiVariable> referencedVariables = RefactoringUtil.collectReferencedVariables(occurence);
740     final List<PsiVariable> modifiedInBody = new ArrayList<PsiVariable>();
741     for (PsiVariable psiVariable : referencedVariables) {
742       if (RefactoringUtil.isModifiedInScope(psiVariable, loopForLoopCondition)) {
743         modifiedInBody.add(psiVariable);
744       }
745     }
746
747     if (!modifiedInBody.isEmpty()) {
748       for (PsiVariable variable : modifiedInBody) {
749         final String message = RefactoringBundle.message("is.modified.in.loop.body", RefactoringUIUtil.getDescription(variable, false));
750         conflicts.putValue(variable, CommonRefactoringUtil.capitalize(message));
751       }
752       conflicts.putValue(occurence, RefactoringBundle.message("introducing.variable.may.break.code.logic"));
753     }
754   }
755
756
757 }