2 * Copyright 2000-2010 JetBrains s.r.o.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
13 package org.jetbrains.plugins.groovy.refactoring.introduce;
15 import com.intellij.codeInsight.highlighting.HighlightManager;
16 import com.intellij.openapi.actionSystem.DataContext;
17 import com.intellij.openapi.application.AccessToken;
18 import com.intellij.openapi.application.WriteAction;
19 import com.intellij.openapi.command.CommandProcessor;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.editor.Editor;
22 import com.intellij.openapi.editor.SelectionModel;
23 import com.intellij.openapi.editor.colors.EditorColors;
24 import com.intellij.openapi.editor.colors.EditorColorsManager;
25 import com.intellij.openapi.editor.markup.RangeHighlighter;
26 import com.intellij.openapi.editor.markup.TextAttributes;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.Pass;
29 import com.intellij.openapi.util.TextRange;
30 import com.intellij.openapi.wm.WindowManager;
31 import com.intellij.psi.*;
32 import com.intellij.psi.search.LocalSearchScope;
33 import com.intellij.psi.search.searches.ReferencesSearch;
34 import com.intellij.psi.util.PsiTreeUtil;
35 import com.intellij.psi.util.TypeConversionUtil;
36 import com.intellij.refactoring.IntroduceTargetChooser;
37 import com.intellij.refactoring.RefactoringActionHandler;
38 import com.intellij.refactoring.RefactoringBundle;
39 import com.intellij.refactoring.util.CommonRefactoringUtil;
40 import com.intellij.util.Function;
41 import com.intellij.util.Processor;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44 import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
45 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
46 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
47 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
49 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
51 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
52 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
53 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
54 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
55 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
56 import org.jetbrains.plugins.groovy.refactoring.NameValidator;
57 import org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldHandler;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
64 * @author Maxim.Medvedev
66 public abstract class GrIntroduceHandlerBase<Settings extends GrIntroduceSettings> implements RefactoringActionHandler {
67 protected abstract String getRefactoringName();
69 protected abstract String getHelpID();
72 protected abstract PsiElement findScope(GrExpression expression, GrVariable variable);
74 protected abstract void checkExpression(GrExpression selectedExpr) throws GrIntroduceRefactoringError;
76 protected abstract void checkVariable(GrVariable variable) throws GrIntroduceRefactoringError;
78 protected abstract void checkOccurrences(PsiElement[] occurrences);
80 protected abstract GrIntroduceDialog<Settings> getDialog(GrIntroduceContext context);
83 public abstract GrVariable runRefactoring(GrIntroduceContext context, Settings settings);
85 public static List<GrExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset) {
86 int correctedOffset = correctOffset(editor, offset);
87 final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
88 final List<GrExpression> expressions = new ArrayList<GrExpression>();
90 for (GrExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, GrExpression.class);
92 expression = PsiTreeUtil.getParentOfType(expression, GrExpression.class)) {
93 if (expressions.contains(expression)) continue;
94 if (expressionIsNotCorrect(expression)) continue;
96 expressions.add(expression);
101 private static boolean expressionIsNotCorrect(GrExpression expression) {
102 if (expression instanceof GrParenthesizedExpression) return true;
103 if (expression instanceof GrSuperReferenceExpression) return true;
104 if (expression.getType() == PsiType.VOID) return true;
105 if (expression instanceof GrAssignmentExpression) return true;
106 if (expression instanceof GrReferenceExpression && expression.getParent() instanceof GrCall) {
107 final GroovyResolveResult resolveResult = ((GrReferenceExpression)expression).advancedResolve();
108 final PsiElement resolved = resolveResult.getElement();
109 return resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty() || resolved instanceof PsiClass;
111 if (expression instanceof GrApplicationStatement) {
112 return !PsiUtil.isExpressionStatement(expression);
114 if (expression instanceof GrClosableBlock && expression.getParent() instanceof GrStringInjection) return true;
119 public static int correctOffset(Editor editor, int offset) {
120 Document document = editor.getDocument();
121 CharSequence text = document.getCharsSequence();
122 int correctedOffset = offset;
123 int textLength = document.getTextLength();
124 if (offset >= textLength) {
125 correctedOffset = textLength - 1;
127 else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
131 if (correctedOffset < 0) {
132 correctedOffset = offset;
135 final char c = text.charAt(correctedOffset);
136 if (!Character.isJavaIdentifierPart(c)) {
137 if (c == ';') {//initially caret on the end of line
140 if (c != ')' && c != ']' && c!='}' && c!='\'' && c!='"' && c!='/') {
141 correctedOffset = offset;
145 return correctedOffset;
149 public static GrVariable findVariableAtCaret(final PsiFile file, final Editor editor, final int offset) {
150 final int correctOffset = correctOffset(editor, offset);
151 final PsiElement elementAtCaret = file.findElementAt(correctOffset);
152 final GrVariable variable = PsiTreeUtil.getParentOfType(elementAtCaret, GrVariable.class);
153 if (variable != null && variable.getNameIdentifierGroovy().getTextRange().contains(correctOffset)) return variable;
157 public void invoke(final @NotNull Project project, final Editor editor, final PsiFile file, final @Nullable DataContext dataContext) {
158 final SelectionModel selectionModel = editor.getSelectionModel();
159 if (!selectionModel.hasSelection()) {
160 final int offset = editor.getCaretModel().getOffset();
162 final List<GrExpression> expressions = collectExpressions(file, editor, offset);
163 if (expressions.isEmpty()) {
164 final GrVariable variable = findVariableAtCaret(file, editor, offset);
165 if (variable == null || variable instanceof GrField || variable instanceof GrParameter) {
166 selectionModel.selectLineAtCaret();
169 final TextRange textRange = variable.getTextRange();
170 selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
173 else if (expressions.size() == 1) {
174 final TextRange textRange = expressions.get(0).getTextRange();
175 selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
178 IntroduceTargetChooser.showChooser(editor, expressions,
179 new Pass<GrExpression>() {
180 public void pass(final GrExpression selectedValue) {
181 invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(),
182 selectedValue.getTextRange().getEndOffset());
185 new Function<GrExpression, String>() {
187 public String fun(GrExpression grExpression) {
188 return grExpression.getText();
194 invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
198 public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
202 public GrIntroduceContext getContext(Project project, Editor editor, GrExpression expression, @Nullable GrVariable variable) {
203 final PsiElement scope = findScope(expression, variable);
205 if (variable == null) {
206 final PsiElement[] occurences = findOccurences(expression, scope);
207 return new GrIntroduceContext(project, editor, expression, occurences, scope, variable);
211 final List<PsiElement> list = Collections.synchronizedList(new ArrayList<PsiElement>());
212 ReferencesSearch.search(variable, new LocalSearchScope(scope)).forEach(new Processor<PsiReference>() {
214 public boolean process(PsiReference psiReference) {
215 final PsiElement element = psiReference.getElement();
216 if (element != null) {
222 return new GrIntroduceContext(project, editor, variable.getInitializerGroovy(), list.toArray(new PsiElement[list.size()]), scope,
227 protected PsiElement[] findOccurences(GrExpression expression, PsiElement scope) {
228 final PsiElement[] occurrences = GroovyRefactoringUtil.getExpressionOccurrences(PsiUtil.skipParentheses(expression, false), scope);
229 if (occurrences == null || occurrences.length == 0) {
230 throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("no.occurences.found"));
235 private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) {
237 PsiDocumentManager.getInstance(project).commitAllDocuments();
238 if (!(file instanceof GroovyFileBase)) {
239 throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("only.in.groovy.files"));
241 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) {
242 throw new GrIntroduceRefactoringError(RefactoringBundle.message("readonly.occurences.found"));
245 GrExpression selectedExpr = findExpression(file, startOffset, endOffset);
246 final GrVariable variable = findVariable(file, startOffset, endOffset);
247 if (variable != null) {
248 checkVariable(variable);
250 else if (selectedExpr != null) {
251 checkExpression(selectedExpr);
254 throw new GrIntroduceRefactoringError(null);
257 final GrIntroduceContext context = getContext(project, editor, selectedExpr, variable);
258 checkOccurrences(context.occurrences);
259 final Settings settings = showDialog(context);
260 if (settings == null) return false;
262 CommandProcessor.getInstance().executeCommand(context.project, new Runnable() {
264 AccessToken accessToken = WriteAction.start();
266 runRefactoring(context, settings);
269 accessToken.finish();
272 }, getRefactoringName(), null);
276 catch (GrIntroduceRefactoringError e) {
277 CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID());
283 public static GrVariable findVariable(PsiFile file, int startOffset, int endOffset) {
284 GrVariable var = GroovyRefactoringUtil.findElementInRange(file, startOffset, endOffset, GrVariable.class);
286 final GrVariableDeclaration variableDeclaration =
287 GroovyRefactoringUtil.findElementInRange(file, startOffset, endOffset, GrVariableDeclaration.class);
288 if (variableDeclaration == null) return null;
289 final GrVariable[] variables = variableDeclaration.getVariables();
290 if (variables.length == 1) {
294 if (var instanceof GrParameter || var instanceof GrField) {
301 public static GrExpression findExpression(PsiFile file, int startOffset, int endOffset) {
302 GrExpression selectedExpr = GroovyRefactoringUtil.findElementInRange(file, startOffset, endOffset, GrExpression.class);
303 while (selectedExpr instanceof GrParenthesizedExpression) selectedExpr = ((GrParenthesizedExpression)selectedExpr).getOperand();
304 if (selectedExpr == null) return null;
305 PsiType type = selectedExpr.getType();
306 if (type != null) type = TypeConversionUtil.erasure(type);
308 if (PsiType.VOID.equals(type)) {
309 throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("selected.expression.has.void.type"));
312 if (expressionIsNotCorrect(selectedExpr)) {
313 throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("selected.block.should.represent.an.expression"));
320 private Settings showDialog(GrIntroduceContext context) {
322 // Add occurences highlighting
323 ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
324 HighlightManager highlightManager = null;
325 if (context.editor != null) {
326 highlightManager = HighlightManager.getInstance(context.project);
327 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
328 TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
329 if (context.occurrences.length > 1) {
330 highlightManager.addOccurrenceHighlights(context.editor, context.occurrences, attributes, true, highlighters);
334 GrIntroduceDialog<Settings> dialog = getDialog(context);
338 if (context.editor != null) {
339 for (RangeHighlighter highlighter : highlighters) {
340 highlightManager.removeSegmentHighlighter(context.editor, highlighter);
343 return dialog.getSettings();
346 if (context.occurrences.length > 1) {
347 WindowManager.getInstance().getStatusBar(context.project)
348 .setInfo(GroovyRefactoringBundle.message("press.escape.to.remove.the.highlighting"));
355 public static PsiElement findAnchor(GrIntroduceContext context,
356 GrIntroduceSettings settings,
357 PsiElement[] occurrences,
358 final PsiElement container) {
359 if (occurrences.length == 0) return null;
360 PsiElement candidate;
361 if (occurrences.length == 1 || !settings.replaceAllOccurrences()) {
362 candidate = context.expression;
363 candidate = findContainingStatement(candidate);
366 GroovyRefactoringUtil.sortOccurrences(occurrences);
367 candidate = occurrences[0];
368 while (candidate != null && !container.equals(candidate.getParent())) {
369 candidate = candidate.getParent();
373 final GrStringInjection injection = PsiTreeUtil.getParentOfType(candidate, GrStringInjection.class);
374 if (injection != null && !injection.getText().contains("\n")) {
375 candidate = findContainingStatement(injection);
378 if (candidate == null) return null;
380 if ((container instanceof GrWhileStatement) &&
381 candidate.equals(((GrWhileStatement)container).getCondition())) {
384 if ((container instanceof GrIfStatement) &&
385 candidate.equals(((GrIfStatement)container).getCondition())) {
388 if ((container instanceof GrForStatement) &&
389 candidate.equals(((GrForStatement)container).getClause())) {
396 private static PsiElement findContainingStatement(PsiElement candidate) {
397 while (candidate != null && !PsiUtil.isExpressionStatement(candidate)) candidate = candidate.getParent();
401 protected static void deleteLocalVar(GrIntroduceContext context) {
402 final GrVariable resolved = GrIntroduceFieldHandler.resolveLocalVar(context);
403 final PsiElement parent = resolved.getParent();
404 if (parent instanceof GrTupleDeclaration) {
405 if (((GrTupleDeclaration)parent).getVariables().length == 1) {
406 parent.getParent().delete();
409 final GrExpression initializerGroovy = resolved.getInitializerGroovy();
410 if (initializerGroovy != null) initializerGroovy.delete();
415 if (((GrVariableDeclaration)parent).getVariables().length == 1) {
424 protected static GrVariable resolveLocalVar(GrIntroduceContext context) {
425 if (context.var != null) return context.var;
426 return (GrVariable)((GrReferenceExpression)context.expression).resolve();
429 public static boolean hasLhs(final PsiElement[] occurrences) {
430 for (PsiElement element : occurrences) {
431 if (element instanceof GrReferenceExpression) {
432 if (PsiUtil.isLValue((GroovyPsiElement)element)) return true;
433 if (ControlFlowUtils.isIncOrDecOperand((GrReferenceExpression)element)) return true;
440 public interface Validator extends NameValidator {
441 boolean isOK(GrIntroduceDialog dialog);