update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / anonymousToInner / AnonymousToInnerHandler.java
blob9d49307e5d7f9948235719878440c0ed7c3c7d87
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.refactoring.anonymousToInner;
18 import com.intellij.codeInsight.ChangeContextUtil;
19 import com.intellij.openapi.actionSystem.DataContext;
20 import com.intellij.openapi.actionSystem.PlatformDataKeys;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.command.CommandProcessor;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.editor.ScrollType;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.psi.*;
28 import com.intellij.psi.codeStyle.CodeStyleManager;
29 import com.intellij.psi.search.LocalSearchScope;
30 import com.intellij.psi.search.searches.ReferencesSearch;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import com.intellij.refactoring.HelpID;
34 import com.intellij.refactoring.RefactoringActionHandler;
35 import com.intellij.refactoring.RefactoringBundle;
36 import com.intellij.refactoring.util.CommonRefactoringUtil;
37 import com.intellij.refactoring.util.classMembers.ElementNeedsThis;
38 import com.intellij.util.IncorrectOperationException;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import java.util.*;
45 public class AnonymousToInnerHandler implements RefactoringActionHandler {
46 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.anonymousToInner.AnonymousToInnerHandler");
48 static final String REFACTORING_NAME = RefactoringBundle.message("anonymousToInner.refactoring.name");
50 private Project myProject;
52 private PsiManager myManager;
54 private PsiAnonymousClass myAnonClass;
55 private PsiClass myTargetClass;
56 protected String myNewClassName;
58 private VariableInfo[] myVariableInfos;
59 protected boolean myMakeStatic;
60 private final Set<PsiTypeParameter> myTypeParametersToCreate = new LinkedHashSet<PsiTypeParameter>();
62 public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
63 if (elements.length == 1 && elements[0] instanceof PsiAnonymousClass) {
64 invoke(project, PlatformDataKeys.EDITOR.getData(dataContext), (PsiAnonymousClass)elements[0]);
68 public void invoke(@NotNull final Project project, Editor editor, final PsiFile file, DataContext dataContext) {
69 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return;
71 final int offset = editor.getCaretModel().getOffset();
72 editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
73 final PsiAnonymousClass anonymousClass = findAnonymousClass(file, offset);
74 if (anonymousClass == null) {
75 showErrorMessage(editor, RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("error.wrong.caret.position.anonymous")));
76 return;
78 invoke(project, editor, anonymousClass);
81 private void showErrorMessage(Editor editor, String message) {
82 CommonRefactoringUtil.showErrorHint(myProject, editor, message, REFACTORING_NAME, HelpID.ANONYMOUS_TO_INNER);
85 public void invoke(final Project project, Editor editor, final PsiAnonymousClass anonymousClass) {
86 myProject = project;
88 myManager = PsiManager.getInstance(myProject);
89 myAnonClass = anonymousClass;
91 PsiClassType baseRef = myAnonClass.getBaseClassType();
93 if (baseRef.resolve() == null) {
94 String message = RefactoringBundle.message("error.cannot.resolve", baseRef.getCanonicalText());
95 showErrorMessage(editor, message);
96 return;
98 PsiElement targetContainer = findTargetContainer(myAnonClass);
99 if (JspPsiUtil.isInJspFile(targetContainer) && targetContainer instanceof PsiFile) {
100 String message = RefactoringBundle.message("error.not.supported.for.jsp", REFACTORING_NAME);
101 showErrorMessage(editor, message);
102 return;
104 LOG.assertTrue(targetContainer instanceof PsiClass);
105 myTargetClass = (PsiClass) targetContainer;
107 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, myTargetClass)) return;
109 Map<PsiVariable,VariableInfo> variableInfoMap = new LinkedHashMap<PsiVariable, VariableInfo>();
110 collectUsedVariables(variableInfoMap, myAnonClass);
111 myVariableInfos = variableInfoMap.values().toArray(new VariableInfo[variableInfoMap.values().size()]);
112 if (!showRefactoringDialog()) return;
114 CommandProcessor.getInstance().executeCommand(
115 myProject, new Runnable() {
116 public void run() {
117 final Runnable action = new Runnable() {
118 public void run() {
119 try {
120 doRefactoring();
121 } catch (IncorrectOperationException e) {
122 LOG.error(e);
126 ApplicationManager.getApplication().runWriteAction(action);
129 REFACTORING_NAME,
130 null
135 protected boolean showRefactoringDialog() {
136 final boolean needsThis = needsThis() || PsiUtil.isInnerClass(myTargetClass);
137 final AnonymousToInnerDialog dialog = new AnonymousToInnerDialog(
138 myProject,
139 myAnonClass,
140 myVariableInfos,
141 needsThis);
142 dialog.show();
143 if (!dialog.isOK()) {
144 return false;
146 myNewClassName = dialog.getClassName();
147 myVariableInfos = dialog.getVariableInfos();
148 myMakeStatic = dialog.isMakeStatic();
149 return true;
152 private void doRefactoring() throws IncorrectOperationException {
153 calculateTypeParametersToCreate();
154 PsiClass aClass = createClass(myNewClassName);
155 myTargetClass.add(aClass);
157 PsiNewExpression newExpr = (PsiNewExpression) myAnonClass.getParent();
158 @NonNls StringBuffer buf = new StringBuffer();
159 buf.append("new ");
160 buf.append(aClass.getName());
161 if (!myTypeParametersToCreate.isEmpty()) {
162 buf.append("<");
163 int idx = 0;
164 //noinspection ForLoopThatDoesntUseLoopVariable
165 for (Iterator<PsiTypeParameter> it = myTypeParametersToCreate.iterator(); it.hasNext(); idx++) {
166 if (idx > 0) buf.append(", ");
167 String typeParamName = it.next().getName();
168 buf.append(typeParamName);
170 buf.append(">");
172 buf.append("(");
173 boolean isFirstParameter = true;
174 for (VariableInfo info : myVariableInfos) {
175 if (info.passAsParameter) {
176 if (isFirstParameter) {
177 isFirstParameter = false;
179 else {
180 buf.append(",");
182 buf.append(info.variable.getName());
185 buf.append(")");
186 PsiExpression newClassExpression = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createExpressionFromText(buf.toString(), null);
187 newExpr.replace(newClassExpression);
190 @Nullable
191 public static PsiAnonymousClass findAnonymousClass(PsiFile file, int offset) {
192 PsiElement element = file.findElementAt(offset);
193 while (element != null) {
194 if (element instanceof PsiAnonymousClass) {
195 return (PsiAnonymousClass) element;
197 if (element instanceof PsiNewExpression) {
198 final PsiNewExpression newExpression = (PsiNewExpression)element;
199 if (newExpression.getAnonymousClass() != null) {
200 return newExpression.getAnonymousClass();
203 element = element.getParent();
205 return null;
208 public static PsiElement findTargetContainer(PsiAnonymousClass anonClass) {
209 PsiElement parent = anonClass.getParent();
210 while (true) {
211 if (parent instanceof PsiClass && !(parent instanceof PsiAnonymousClass)) {
212 return parent;
214 if (parent instanceof PsiFile) {
215 return parent;
217 parent = parent.getParent();
221 private void collectUsedVariables(final Map<PsiVariable, VariableInfo> variableInfoMap,
222 PsiElement scope) {
223 scope.accept(new JavaRecursiveElementWalkingVisitor() {
224 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
225 if (expression.getQualifierExpression() == null) {
226 PsiElement refElement = expression.resolve();
227 if (refElement instanceof PsiVariable && !(refElement instanceof PsiField)) {
228 PsiVariable var = (PsiVariable)refElement;
230 final PsiClass containingClass = PsiTreeUtil.getParentOfType(var, PsiClass.class);
231 if (PsiTreeUtil.isAncestor(containingClass, myAnonClass, true)) {
232 saveVariable(variableInfoMap, var, expression);
236 super.visitReferenceExpression(expression);
241 private Boolean cachedNeedsThis = null;
242 private boolean needsThis() {
243 if(cachedNeedsThis == null) {
245 ElementNeedsThis memberNeedsThis = new ElementNeedsThis(myTargetClass, myAnonClass);
246 myAnonClass.accept(memberNeedsThis);
247 class HasExplicitThis extends JavaRecursiveElementWalkingVisitor {
248 boolean hasExplicitThis = false;
249 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
252 @Override public void visitThisExpression(PsiThisExpression expression) {
253 hasExplicitThis = true;
256 final HasExplicitThis hasExplicitThis = new HasExplicitThis();
257 PsiExpressionList argList = myAnonClass.getArgumentList();
258 if (argList != null) argList.accept(hasExplicitThis);
259 cachedNeedsThis = memberNeedsThis.usesMembers() || hasExplicitThis.hasExplicitThis;
261 return cachedNeedsThis.booleanValue();
265 private void saveVariable(Map<PsiVariable, VariableInfo> variableInfoMap,
266 PsiVariable var,
267 PsiReferenceExpression usage) {
268 VariableInfo info = variableInfoMap.get(var);
269 if (info == null) {
270 info = new VariableInfo(var);
271 variableInfoMap.put(var, info);
273 info.saveInField |= !isUsedInInitializer(usage);
276 private boolean isUsedInInitializer(PsiElement usage) {
277 PsiElement parent = usage.getParent();
278 while (!myAnonClass.equals(parent)) {
279 if (parent instanceof PsiExpressionList) {
280 PsiExpressionList expressionList = (PsiExpressionList) parent;
281 if (myAnonClass.equals(expressionList.getParent())) {
282 return true;
284 } else if (parent instanceof PsiClassInitializer && myAnonClass.equals(((PsiClassInitializer)parent).getContainingClass())) {
285 //class initializers will be moved to constructor to be generated
286 return true;
288 parent = parent.getParent();
290 return false;
293 private PsiClass createClass(String name) throws IncorrectOperationException {
294 PsiElementFactory factory = JavaPsiFacade.getInstance(myAnonClass.getProject()).getElementFactory();
295 CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(myProject);
296 final PsiNewExpression newExpression = (PsiNewExpression) myAnonClass.getParent();
297 final PsiMethod superConstructor = newExpression.resolveConstructor();
299 PsiClass aClass = factory.createClass(name);
300 final PsiTypeParameterList typeParameterList = aClass.getTypeParameterList();
301 LOG.assertTrue(typeParameterList != null);
302 for (PsiTypeParameter typeParameter : myTypeParametersToCreate) {
303 typeParameterList.add((typeParameter));
306 if (!myTargetClass.isInterface()) {
307 PsiUtil.setModifierProperty(aClass, PsiModifier.PRIVATE, true);
309 PsiModifierListOwner owner = PsiTreeUtil.getParentOfType(myAnonClass, PsiModifierListOwner.class);
310 if (owner != null && owner.hasModifierProperty(PsiModifier.STATIC)) {
311 PsiUtil.setModifierProperty(aClass, PsiModifier.STATIC, true);
313 PsiJavaCodeReferenceElement baseClassRef = myAnonClass.getBaseClassReference();
314 PsiClass baseClass = (PsiClass)baseClassRef.resolve();
315 if (baseClass == null || !CommonClassNames.JAVA_LANG_OBJECT.equals(baseClass.getQualifiedName())) {
316 PsiReferenceList refList = baseClass != null && baseClass.isInterface() ?
317 aClass.getImplementsList() :
318 aClass.getExtendsList();
319 if (refList != null) refList.add(baseClassRef);
322 renameReferences(myAnonClass);
323 copyClassBody(myAnonClass, aClass, myVariableInfos.length > 0);
325 if (myVariableInfos.length > 0) {
326 createFields(aClass);
329 PsiExpressionList argList = newExpression.getArgumentList();
330 assert argList != null;
331 PsiExpression[] originalExpressions = argList.getExpressions();
332 final PsiReferenceList superConstructorThrowsList =
333 superConstructor != null && superConstructor.getThrowsList().getReferencedTypes().length > 0
334 ? superConstructor.getThrowsList()
335 : null;
336 if (myVariableInfos.length > 0 || originalExpressions.length > 0 || superConstructorThrowsList != null) {
337 PsiMethod constructor = factory.createConstructor();
338 if (superConstructorThrowsList != null) {
339 constructor.getThrowsList().replace(superConstructorThrowsList);
341 if (originalExpressions.length > 0) {
342 createSuperStatement(constructor, originalExpressions);
344 if (myVariableInfos.length > 0) {
345 fillParameterList(constructor);
346 createAssignmentStatements(constructor);
348 appendInitializers(constructor);
351 constructor = (PsiMethod) codeStyleManager.reformat(constructor);
352 aClass.add(constructor);
355 if (!needsThis() && myMakeStatic) {
356 PsiUtil.setModifierProperty(aClass, PsiModifier.STATIC, true);
358 PsiElement lastChild = aClass.getLastChild();
359 if (lastChild instanceof PsiJavaToken && ((PsiJavaToken)lastChild).getTokenType() == JavaTokenType.SEMICOLON) {
360 lastChild.delete();
363 return aClass;
366 private void appendInitializers(final PsiMethod constructor) throws IncorrectOperationException {
367 PsiCodeBlock constructorBody = constructor.getBody();
368 assert constructorBody != null;
370 List<PsiElement> toAdd = new ArrayList<PsiElement>();
371 for (PsiClassInitializer initializer : myAnonClass.getInitializers()) {
372 if (!initializer.hasModifierProperty(PsiModifier.STATIC)) {
373 toAdd.add(initializer);
376 for (PsiField field : myAnonClass.getFields()) {
377 if (!field.hasModifierProperty(PsiModifier.STATIC) && field.getInitializer() != null) {
378 toAdd.add(field);
382 Collections.sort(toAdd, new Comparator<PsiElement>() {
383 public int compare(PsiElement e1, PsiElement e2) {
384 return e1.getTextRange().getStartOffset() - e2.getTextRange().getStartOffset();
388 for (PsiElement element : toAdd) {
389 if (element instanceof PsiClassInitializer) {
390 PsiClassInitializer initializer = (PsiClassInitializer) element;
391 final PsiCodeBlock initializerBody = initializer.getBody();
392 PsiElement firstBodyElement = initializerBody.getFirstBodyElement();
393 if (firstBodyElement != null) {
394 constructorBody.addRange(firstBodyElement, initializerBody.getLastBodyElement());
396 } else {
397 PsiField field = (PsiField) element;
398 final PsiExpressionStatement statement = (PsiExpressionStatement)JavaPsiFacade.getInstance(myManager.getProject())
399 .getElementFactory()
400 .createStatementFromText(field.getName() + "= 0;", null);
401 PsiExpression rightExpression = ((PsiAssignmentExpression) statement.getExpression()).getRExpression();
402 assert rightExpression != null;
403 PsiExpression fieldInitializer = field.getInitializer();
404 assert fieldInitializer != null;
405 rightExpression.replace(fieldInitializer);
406 constructorBody.add(statement);
411 private static void copyClassBody(PsiClass sourceClass,
412 PsiClass targetClass,
413 boolean appendInitializersToConstructor) throws IncorrectOperationException {
414 PsiElement lbrace = sourceClass.getLBrace();
415 PsiElement rbrace = sourceClass.getRBrace();
416 if (lbrace != null) {
417 targetClass.addRange(lbrace.getNextSibling(), rbrace != null ? rbrace.getPrevSibling() : sourceClass.getLastChild());
418 if (appendInitializersToConstructor) { //see SCR 41692
419 final PsiClassInitializer[] initializers = targetClass.getInitializers();
420 for (PsiClassInitializer initializer : initializers) {
421 if (!initializer.hasModifierProperty(PsiModifier.STATIC)) initializer.delete();
423 final PsiField[] fields = targetClass.getFields();
424 for (PsiField field : fields) {
425 PsiExpression initializer = field.getInitializer();
426 if (!field.hasModifierProperty(PsiModifier.STATIC) && initializer != null) {
427 initializer.delete();
434 private void fillParameterList(PsiMethod constructor) throws IncorrectOperationException {
435 PsiElementFactory factory = JavaPsiFacade.getInstance(constructor.getProject()).getElementFactory();
436 PsiParameterList parameterList = constructor.getParameterList();
437 for (VariableInfo info : myVariableInfos) {
438 if (info.passAsParameter) {
439 parameterList.add(factory.createParameter(info.parameterName, info.variable.getType()));
444 private void createFields(PsiClass aClass) throws IncorrectOperationException {
445 PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory();
446 for (VariableInfo info : myVariableInfos) {
447 if (info.saveInField) {
448 PsiType type = info.variable.getType();
449 if (type instanceof PsiEllipsisType) type = ((PsiEllipsisType)type).toArrayType();
450 PsiField field = factory.createField(info.fieldName, type);
451 PsiUtil.setModifierProperty(field, PsiModifier.FINAL, true);
452 aClass.add(field);
457 private void createAssignmentStatements(PsiMethod constructor) throws IncorrectOperationException {
458 PsiElementFactory factory = JavaPsiFacade.getInstance(constructor.getProject()).getElementFactory();
459 for (VariableInfo info : myVariableInfos) {
460 if (info.saveInField) {
461 @NonNls String text = info.fieldName + "=a;";
462 boolean useThis = info.passAsParameter && info.parameterName.equals(info.fieldName);
463 if (useThis) {
464 text = "this." + text;
466 PsiExpressionStatement statement = (PsiExpressionStatement)factory.createStatementFromText(text, null);
467 statement = (PsiExpressionStatement)CodeStyleManager.getInstance(myProject).reformat(statement);
468 // in order for "..." trick to work, the statement must be added to constructor first
469 PsiCodeBlock constructorBody = constructor.getBody();
470 assert constructorBody != null;
471 statement = (PsiExpressionStatement)constructorBody.add(statement);
473 PsiAssignmentExpression assignment = (PsiAssignmentExpression)statement.getExpression();
474 PsiReferenceExpression rExpr = (PsiReferenceExpression)assignment.getRExpression();
475 assert rExpr != null;
476 if (info.passAsParameter) {
477 rExpr.replace(factory.createExpressionFromText(info.parameterName, null));
479 else {
480 rExpr.delete();
486 private void renameReferences(PsiElement scope) throws IncorrectOperationException {
487 PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory();
488 for (VariableInfo info : myVariableInfos) {
489 for (PsiReference reference : ReferencesSearch.search(info.variable, new LocalSearchScope(scope))) {
490 PsiElement ref = reference.getElement();
491 PsiIdentifier identifier = (PsiIdentifier)((PsiJavaCodeReferenceElement)ref).getReferenceNameElement();
492 assert identifier != null;
493 boolean renameToFieldName = !isUsedInInitializer(ref);
494 PsiIdentifier newNameIdentifier = factory.createIdentifier(renameToFieldName ? info.fieldName : info.parameterName);
495 if (renameToFieldName) {
496 identifier.replace(newNameIdentifier);
498 else {
499 if (info.passAsParameter) {
500 identifier.replace(newNameIdentifier);
507 private void createSuperStatement(PsiMethod constructor, PsiExpression[] paramExpressions) throws IncorrectOperationException {
508 PsiCodeBlock body = constructor.getBody();
509 assert body != null;
510 final PsiElementFactory factory = JavaPsiFacade.getInstance(constructor.getProject()).getElementFactory();
512 PsiStatement statement = factory.createStatementFromText("super();", null);
513 statement = (PsiStatement) CodeStyleManager.getInstance(myProject).reformat(statement);
514 statement = (PsiStatement) body.add(statement);
516 PsiMethodCallExpression methodCall = (PsiMethodCallExpression) ((PsiExpressionStatement) statement).getExpression();
517 PsiExpressionList exprList = methodCall.getArgumentList();
521 final PsiThisExpression qualifiedThis =
522 (PsiThisExpression) factory.createExpressionFromText("A.this", null);
523 final PsiJavaCodeReferenceElement targetClassRef = factory.createClassReferenceElement(myTargetClass);
524 PsiJavaCodeReferenceElement thisQualifier = qualifiedThis.getQualifier();
525 assert thisQualifier != null;
526 thisQualifier.replace(targetClassRef);
528 for (PsiExpression expr : paramExpressions) {
529 ChangeContextUtil.encodeContextInfo(expr, true);
530 final PsiElement newExpr = exprList.add(expr);
531 ChangeContextUtil.decodeContextInfo(newExpr, myTargetClass, qualifiedThis);
535 class SupersConvertor extends JavaRecursiveElementVisitor {
536 @Override public void visitThisExpression(PsiThisExpression expression) {
537 try {
538 final PsiThisExpression qualifiedThis =
539 (PsiThisExpression) factory.createExpressionFromText("A.this", null);
540 final PsiJavaCodeReferenceElement targetClassRef = factory.createClassReferenceElement(myTargetClass);
541 PsiJavaCodeReferenceElement thisQualifier = qualifiedThis.getQualifier();
542 assert thisQualifier != null;
543 thisQualifier.replace(targetClassRef);
544 expression.replace(qualifiedThis);
545 } catch (IncorrectOperationException e) {
546 LOG.error(e);
550 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
554 final SupersConvertor supersConvertor = new SupersConvertor();
555 methodCall.getArgumentList().accept(supersConvertor);
558 private void calculateTypeParametersToCreate () {
559 myAnonClass.accept(new JavaRecursiveElementWalkingVisitor() {
560 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
561 super.visitReferenceElement(reference);
562 final PsiElement resolved = reference.resolve();
563 if (resolved instanceof PsiTypeParameter) {
564 final PsiTypeParameterListOwner owner = ((PsiTypeParameter)resolved).getOwner();
565 if (owner != null && !PsiTreeUtil.isAncestor(myAnonClass, owner, false)) {
566 myTypeParametersToCreate.add((PsiTypeParameter)resolved);