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
;
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")));
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
) {
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
);
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
);
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() {
117 final Runnable action
= new Runnable() {
121 } catch (IncorrectOperationException e
) {
126 ApplicationManager
.getApplication().runWriteAction(action
);
135 protected boolean showRefactoringDialog() {
136 final boolean needsThis
= needsThis() || PsiUtil
.isInnerClass(myTargetClass
);
137 final AnonymousToInnerDialog dialog
= new AnonymousToInnerDialog(
143 if (!dialog
.isOK()) {
146 myNewClassName
= dialog
.getClassName();
147 myVariableInfos
= dialog
.getVariableInfos();
148 myMakeStatic
= dialog
.isMakeStatic();
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();
160 buf
.append(aClass
.getName());
161 if (!myTypeParametersToCreate
.isEmpty()) {
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
);
173 boolean isFirstParameter
= true;
174 for (VariableInfo info
: myVariableInfos
) {
175 if (info
.passAsParameter
) {
176 if (isFirstParameter
) {
177 isFirstParameter
= false;
182 buf
.append(info
.variable
.getName());
186 PsiExpression newClassExpression
= JavaPsiFacade
.getInstance(myManager
.getProject()).getElementFactory().createExpressionFromText(buf
.toString(), null);
187 newExpr
.replace(newClassExpression
);
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();
208 public static PsiElement
findTargetContainer(PsiAnonymousClass anonClass
) {
209 PsiElement parent
= anonClass
.getParent();
211 if (parent
instanceof PsiClass
&& !(parent
instanceof PsiAnonymousClass
)) {
214 if (parent
instanceof PsiFile
) {
217 parent
= parent
.getParent();
221 private void collectUsedVariables(final Map
<PsiVariable
, VariableInfo
> variableInfoMap
,
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
,
267 PsiReferenceExpression usage
) {
268 VariableInfo info
= variableInfoMap
.get(var
);
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())) {
284 } else if (parent
instanceof PsiClassInitializer
&& myAnonClass
.equals(((PsiClassInitializer
)parent
).getContainingClass())) {
285 //class initializers will be moved to constructor to be generated
288 parent
= parent
.getParent();
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()
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
) {
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) {
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());
397 PsiField field
= (PsiField
) element
;
398 final PsiExpressionStatement statement
= (PsiExpressionStatement
)JavaPsiFacade
.getInstance(myManager
.getProject())
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);
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
);
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));
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
);
499 if (info
.passAsParameter
) {
500 identifier
.replace(newNameIdentifier
);
507 private void createSuperStatement(PsiMethod constructor
, PsiExpression
[] paramExpressions
) throws IncorrectOperationException
{
508 PsiCodeBlock body
= constructor
.getBody();
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
) {
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
) {
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
);