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
.inline
;
18 import com
.intellij
.codeInsight
.ChangeContextUtil
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.util
.Key
;
21 import com
.intellij
.openapi
.util
.Pair
;
22 import com
.intellij
.openapi
.util
.text
.StringUtil
;
23 import com
.intellij
.patterns
.ElementPattern
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
26 import com
.intellij
.psi
.impl
.source
.codeStyle
.CodeEditUtil
;
27 import com
.intellij
.psi
.search
.ProjectScope
;
28 import com
.intellij
.psi
.util
.PsiTreeUtil
;
29 import com
.intellij
.psi
.util
.PsiUtil
;
30 import com
.intellij
.refactoring
.util
.RefactoringUtil
;
31 import com
.intellij
.util
.IncorrectOperationException
;
32 import com
.intellij
.util
.ProcessingContext
;
33 import org
.jetbrains
.annotations
.NonNls
;
34 import org
.jetbrains
.annotations
.Nullable
;
36 import java
.util
.ArrayList
;
37 import java
.util
.HashMap
;
38 import java
.util
.List
;
41 import static com
.intellij
.patterns
.PlatformPatterns
.psiElement
;
42 import static com
.intellij
.patterns
.PsiJavaPatterns
.psiExpressionStatement
;
47 class InlineToAnonymousConstructorProcessor
{
48 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.inline.InlineToAnonymousConstructorProcessor");
50 private static final Key
<PsiAssignmentExpression
> ourAssignmentKey
= Key
.create("assignment");
51 private static final Key
<PsiCallExpression
> ourCallKey
= Key
.create("call");
52 public static final ElementPattern ourNullPattern
= psiElement(PsiLiteralExpression
.class).withText(PsiKeyword
.NULL
);
53 private static final ElementPattern ourAssignmentPattern
= psiExpressionStatement().withChild(psiElement(PsiAssignmentExpression
.class).save(ourAssignmentKey
));
54 private static final ElementPattern ourSuperCallPattern
= psiExpressionStatement().withFirstChild(
55 psiElement(PsiMethodCallExpression
.class).save(ourCallKey
).withFirstChild(psiElement().withText(PsiKeyword
.SUPER
)));
56 private static final ElementPattern ourThisCallPattern
= psiExpressionStatement().withFirstChild(psiElement(PsiMethodCallExpression
.class).withFirstChild(
57 psiElement().withText(PsiKeyword
.THIS
)));
59 private final PsiClass myClass
;
60 private final PsiNewExpression myNewExpression
;
61 private final PsiType mySuperType
;
62 private final Map
<String
, PsiExpression
> myFieldInitializers
= new HashMap
<String
, PsiExpression
>();
63 private final Map
<PsiParameter
, PsiVariable
> myLocalsForParameters
= new HashMap
<PsiParameter
, PsiVariable
>();
64 private final PsiStatement myNewStatement
;
65 private final PsiElementFactory myElementFactory
;
66 private PsiMethod myConstructor
;
67 private PsiExpressionList myConstructorArguments
;
68 private PsiParameterList myConstructorParameters
;
70 public InlineToAnonymousConstructorProcessor(final PsiClass aClass
, final PsiNewExpression psiNewExpression
,
71 final PsiType superType
) {
73 myNewExpression
= psiNewExpression
;
74 mySuperType
= superType
;
75 myNewStatement
= PsiTreeUtil
.getParentOfType(myNewExpression
, PsiStatement
.class);
76 myElementFactory
= JavaPsiFacade
.getInstance(myClass
.getProject()).getElementFactory();
79 public void run() throws IncorrectOperationException
{
80 checkInlineChainingConstructor();
81 JavaResolveResult classResolveResult
= myNewExpression
.getClassReference().advancedResolve(false);
82 JavaResolveResult methodResolveResult
= myNewExpression
.resolveMethodGenerics();
83 myConstructor
= (PsiMethod
) methodResolveResult
.getElement();
84 myConstructorArguments
= myNewExpression
.getArgumentList();
86 PsiSubstitutor classResolveSubstitutor
= classResolveResult
.getSubstitutor();
87 PsiType substType
= classResolveSubstitutor
.substitute(mySuperType
);
89 PsiTypeParameter
[] typeParams
= myClass
.getTypeParameters();
90 PsiType
[] substitutedParameters
= new PsiType
[typeParams
.length
];
91 for(int i
=0; i
< typeParams
.length
; i
++) {
92 substitutedParameters
[i
] = classResolveSubstitutor
.substitute(typeParams
[i
]);
95 @NonNls StringBuilder builder
= new StringBuilder("new ");
96 builder
.append(substType
.getCanonicalText());
97 builder
.append("() {}");
99 PsiNewExpression superNewExpressionTemplate
= (PsiNewExpression
) myElementFactory
.createExpressionFromText(builder
.toString(),
100 myNewExpression
.getContainingFile());
101 PsiClassInitializer initializerBlock
= myElementFactory
.createClassInitializer();
102 PsiVariable outerClassLocal
= null;
103 if (myNewExpression
.getQualifier() != null && myClass
.getContainingClass() != null) {
104 outerClassLocal
= generateOuterClassLocal();
106 if (myConstructor
!= null) {
107 myConstructorParameters
= myConstructor
.getParameterList();
109 final PsiExpressionList argumentList
= superNewExpressionTemplate
.getArgumentList();
110 assert argumentList
!= null;
112 if (myNewStatement
!= null) {
113 generateLocalsForArguments();
115 analyzeConstructor(initializerBlock
.getBody());
116 addSuperConstructorArguments(argumentList
);
119 ChangeContextUtil
.encodeContextInfo(myClass
.getNavigationElement(), true);
120 PsiClass classCopy
= (PsiClass
) myClass
.getNavigationElement().copy();
121 ChangeContextUtil
.clearContextInfo(myClass
);
122 final PsiClass anonymousClass
= superNewExpressionTemplate
.getAnonymousClass();
123 assert anonymousClass
!= null;
125 int fieldCount
= myClass
.getFields().length
;
126 int processedFields
= 0;
127 PsiJavaToken token
= anonymousClass
.getRBrace();
128 if (initializerBlock
.getBody().getStatements().length
> 0 && fieldCount
== 0) {
129 insertInitializerBefore(initializerBlock
, anonymousClass
, token
);
132 for(PsiElement child
: classCopy
.getChildren()) {
133 if ((child
instanceof PsiMethod
&& !((PsiMethod
) child
).isConstructor()) ||
134 child
instanceof PsiClassInitializer
|| child
instanceof PsiClass
) {
135 if (!myFieldInitializers
.isEmpty() || !myLocalsForParameters
.isEmpty() || classResolveSubstitutor
!= PsiSubstitutor
.EMPTY
|| outerClassLocal
!= null) {
136 replaceReferences((PsiMember
) child
, substitutedParameters
, outerClassLocal
);
138 child
= anonymousClass
.addBefore(child
, token
);
140 else if (child
instanceof PsiField
) {
141 PsiField field
= (PsiField
) child
;
142 replaceReferences(field
, substitutedParameters
, outerClassLocal
);
143 PsiExpression initializer
= myFieldInitializers
.get(field
.getName());
144 field
= (PsiField
) anonymousClass
.addBefore(field
, token
);
145 if (initializer
!= null) {
146 field
.setInitializer(initializer
);
149 if (processedFields
== fieldCount
&& initializerBlock
.getBody().getStatements().length
> 0) {
150 insertInitializerBefore(initializerBlock
, anonymousClass
, token
);
154 if (PsiTreeUtil
.getChildrenOfType(anonymousClass
, PsiMember
.class) == null) {
155 anonymousClass
.deleteChildRange(anonymousClass
.getLBrace(), anonymousClass
.getRBrace());
157 PsiNewExpression superNewExpression
= (PsiNewExpression
) myNewExpression
.replace(superNewExpressionTemplate
);
158 superNewExpression
= (PsiNewExpression
)ChangeContextUtil
.decodeContextInfo(superNewExpression
, superNewExpression
.getAnonymousClass(), null);
159 JavaCodeStyleManager
.getInstance(superNewExpression
.getProject()).shortenClassReferences(superNewExpression
);
162 private void insertInitializerBefore(final PsiClassInitializer initializerBlock
, final PsiClass anonymousClass
, final PsiJavaToken token
)
163 throws IncorrectOperationException
{
164 anonymousClass
.addBefore(CodeEditUtil
.createLineFeed(token
.getManager()), token
);
165 anonymousClass
.addBefore(initializerBlock
, token
);
166 anonymousClass
.addBefore(CodeEditUtil
.createLineFeed(token
.getManager()), token
);
169 private void checkInlineChainingConstructor() {
171 PsiMethod constructor
= myNewExpression
.resolveConstructor();
172 if (constructor
== null || !InlineMethodHandler
.isChainingConstructor(constructor
)) break;
173 InlineMethodProcessor
.inlineConstructorCall(myNewExpression
);
177 private void analyzeConstructor(final PsiCodeBlock initializerBlock
) throws IncorrectOperationException
{
178 PsiCodeBlock body
= myConstructor
.getBody();
180 for(PsiElement child
: body
.getChildren()) {
181 if (child
instanceof PsiStatement
) {
182 PsiStatement stmt
= (PsiStatement
) child
;
183 ProcessingContext context
= new ProcessingContext();
184 if (ourAssignmentPattern
.accepts(stmt
, context
)) {
185 PsiAssignmentExpression expression
= context
.get(ourAssignmentKey
);
186 if (processAssignmentInConstructor(expression
)) {
187 initializerBlock
.addBefore(replaceParameterReferences(stmt
, null, false), initializerBlock
.getRBrace());
190 else if (!ourSuperCallPattern
.accepts(stmt
) && !ourThisCallPattern
.accepts(stmt
)) {
191 replaceParameterReferences(stmt
, new ArrayList
<PsiReferenceExpression
>(), false);
192 initializerBlock
.addBefore(stmt
, initializerBlock
.getRBrace());
195 else if (child
instanceof PsiComment
) {
196 if (child
.getPrevSibling() instanceof PsiWhiteSpace
) {
197 initializerBlock
.addBefore(child
.getPrevSibling(), initializerBlock
.getRBrace());
199 initializerBlock
.addBefore(child
, initializerBlock
.getRBrace());
204 private boolean processAssignmentInConstructor(final PsiAssignmentExpression expression
) {
205 if (expression
.getLExpression() instanceof PsiReferenceExpression
) {
206 PsiReferenceExpression lExpr
= (PsiReferenceExpression
) expression
.getLExpression();
207 final PsiExpression rExpr
= expression
.getRExpression();
208 if (rExpr
== null) return false;
209 final PsiElement psiElement
= lExpr
.resolve();
210 if (psiElement
instanceof PsiField
) {
211 PsiField field
= (PsiField
) psiElement
;
212 if (myClass
.getManager().areElementsEquivalent(field
.getContainingClass(), myClass
)) {
213 final List
<PsiReferenceExpression
> localVarRefs
= new ArrayList
<PsiReferenceExpression
>();
214 final PsiExpression initializer
;
216 initializer
= (PsiExpression
) replaceParameterReferences((PsiExpression
)rExpr
.copy(), localVarRefs
, false);
218 catch (IncorrectOperationException e
) {
222 if (!localVarRefs
.isEmpty()) {
226 myFieldInitializers
.put(field
.getName(), initializer
);
229 else if (psiElement
instanceof PsiVariable
) {
236 public static boolean isConstant(final PsiExpression expr
) {
237 Object constantValue
= JavaPsiFacade
.getInstance(expr
.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr
);
238 return constantValue
!= null || ourNullPattern
.accepts(expr
);
241 private PsiVariable
generateOuterClassLocal() {
242 PsiClass outerClass
= myClass
.getContainingClass();
243 assert outerClass
!= null;
244 return generateLocal(StringUtil
.decapitalize(outerClass
.getName()),
245 myElementFactory
.createType(outerClass
), myNewExpression
.getQualifier());
248 private PsiVariable
generateLocal(final String baseName
, final PsiType type
, final PsiExpression initializer
) {
249 final JavaCodeStyleManager codeStyleManager
= JavaCodeStyleManager
.getInstance(myClass
.getProject());
251 String baseNameForIndex
= baseName
;
255 localName
= codeStyleManager
.suggestUniqueVariableName(baseNameForIndex
, myNewExpression
, true);
256 if (myClass
.findFieldByName(localName
, false) == null) {
260 baseNameForIndex
= baseName
+ index
;
263 final PsiDeclarationStatement declaration
= myElementFactory
.createVariableDeclarationStatement(localName
, type
, initializer
);
264 PsiVariable variable
= (PsiVariable
)declaration
.getDeclaredElements()[0];
265 PsiUtil
.setModifierProperty(variable
, PsiModifier
.FINAL
, true);
266 myNewStatement
.getParent().addBefore(declaration
, myNewStatement
);
269 catch (IncorrectOperationException e
) {
275 private void generateLocalsForArguments() {
276 PsiExpression
[] expressions
= myConstructorArguments
.getExpressions();
277 for (int i
= 0; i
< expressions
.length
; i
++) {
278 PsiExpression expr
= expressions
[i
];
279 PsiParameter parameter
= myConstructorParameters
.getParameters()[i
];
280 if (parameter
.isVarArgs()) {
281 PsiEllipsisType ellipsisType
= (PsiEllipsisType
)parameter
.getType();
282 PsiType baseType
= ellipsisType
.getComponentType();
283 @NonNls StringBuilder exprBuilder
= new StringBuilder("new ");
284 exprBuilder
.append(baseType
.getCanonicalText());
285 exprBuilder
.append("[] { }");
287 PsiNewExpression newExpr
= (PsiNewExpression
) myElementFactory
.createExpressionFromText(exprBuilder
.toString(), myClass
);
288 PsiArrayInitializerExpression arrayInitializer
= newExpr
.getArrayInitializer();
289 assert arrayInitializer
!= null;
290 for(int j
=i
; j
< expressions
.length
; j
++) {
291 arrayInitializer
.add(expressions
[j
]);
294 PsiVariable variable
= generateLocal(parameter
.getName(), ellipsisType
.toArrayType(), newExpr
);
295 myLocalsForParameters
.put(parameter
, variable
);
297 catch (IncorrectOperationException e
) {
303 else if (!isConstant(expr
)) {
304 PsiVariable variable
= generateLocal(parameter
.getName(), parameter
.getType(), expr
);
305 myLocalsForParameters
.put(parameter
, variable
);
310 private void addSuperConstructorArguments(PsiExpressionList argumentList
) throws IncorrectOperationException
{
311 final PsiCodeBlock body
= myConstructor
.getBody();
313 PsiStatement
[] statements
= body
.getStatements();
314 if (statements
.length
== 0) {
317 ProcessingContext context
= new ProcessingContext();
318 if (!ourSuperCallPattern
.accepts(statements
[0], context
)) {
321 PsiExpressionList superArguments
= context
.get(ourCallKey
).getArgumentList();
322 if (superArguments
!= null) {
323 for(PsiExpression argument
: superArguments
.getExpressions()) {
324 final PsiElement superArgument
= replaceParameterReferences(argument
.copy(), new ArrayList
<PsiReferenceExpression
>(), true);
325 argumentList
.add(superArgument
);
330 private PsiElement
replaceParameterReferences(PsiElement argument
,
331 @Nullable final List
<PsiReferenceExpression
> localVarRefs
,
332 final boolean replaceFieldsWithInitializers
) throws IncorrectOperationException
{
333 if (argument
instanceof PsiReferenceExpression
) {
334 PsiElement element
= ((PsiReferenceExpression
)argument
).resolve();
335 if (element
instanceof PsiParameter
) {
336 PsiParameter parameter
= (PsiParameter
)element
;
337 if (myLocalsForParameters
.containsKey(parameter
)) {
338 return argument
.replace(getParameterReference(parameter
));
340 int index
= myConstructorParameters
.getParameterIndex(parameter
);
341 return argument
.replace(myConstructorArguments
.getExpressions() [index
]);
345 final List
<Pair
<PsiReferenceExpression
, PsiParameter
>> parameterReferences
= new ArrayList
<Pair
<PsiReferenceExpression
, PsiParameter
>>();
346 final Map
<PsiElement
, PsiElement
> elementsToReplace
= new HashMap
<PsiElement
, PsiElement
>();
347 argument
.accept(new JavaRecursiveElementWalkingVisitor() {
348 @Override public void visitReferenceExpression(final PsiReferenceExpression expression
) {
349 super.visitReferenceExpression(expression
);
350 final PsiElement psiElement
= expression
.resolve();
351 if (psiElement
instanceof PsiParameter
) {
352 parameterReferences
.add(new Pair
<PsiReferenceExpression
, PsiParameter
>(expression
, (PsiParameter
) psiElement
));
354 else if ((psiElement
instanceof PsiField
|| psiElement
instanceof PsiMethod
) &&
355 ((PsiMember
) psiElement
).getContainingClass() == myClass
.getSuperClass()) {
356 PsiMember member
= (PsiMember
) psiElement
;
357 if (member
.hasModifierProperty(PsiModifier
.STATIC
) &&
358 expression
.getQualifierExpression() == null) {
359 final String qualifiedText
= myClass
.getSuperClass().getQualifiedName() + "." + member
.getName();
361 final PsiExpression replacement
= myElementFactory
.createExpressionFromText(qualifiedText
, myClass
);
362 elementsToReplace
.put(expression
, replacement
);
364 catch (IncorrectOperationException e
) {
369 else if (psiElement
instanceof PsiVariable
) {
370 if (localVarRefs
!= null) {
371 localVarRefs
.add(expression
);
373 if (replaceFieldsWithInitializers
&& psiElement
instanceof PsiField
&& ((PsiField
) psiElement
).getContainingClass() == myClass
) {
374 final PsiExpression initializer
= ((PsiField
)psiElement
).getInitializer();
375 if (isConstant(initializer
)) {
376 elementsToReplace
.put(expression
, initializer
);
382 for (Pair
<PsiReferenceExpression
, PsiParameter
> pair
: parameterReferences
) {
383 PsiReferenceExpression ref
= pair
.first
;
384 PsiParameter param
= pair
.second
;
385 if (myLocalsForParameters
.containsKey(param
)) {
386 ref
.replace(getParameterReference(param
));
389 int index
= myConstructorParameters
.getParameterIndex(param
);
390 if (ref
== argument
) {
391 argument
= argument
.replace(myConstructorArguments
.getExpressions() [index
]);
394 ref
.replace(myConstructorArguments
.getExpressions() [index
]);
398 return RefactoringUtil
.replaceElementsWithMap(argument
, elementsToReplace
);
401 private PsiExpression
getParameterReference(final PsiParameter parameter
) throws IncorrectOperationException
{
402 PsiVariable variable
= myLocalsForParameters
.get(parameter
);
403 return myElementFactory
.createExpressionFromText(variable
.getName(), myClass
);
406 private void replaceReferences(final PsiMember method
,
407 final PsiType
[] substitutedParameters
, final PsiVariable outerClassLocal
) throws IncorrectOperationException
{
408 final Map
<PsiElement
, PsiElement
> elementsToReplace
= new HashMap
<PsiElement
, PsiElement
>();
409 method
.accept(new JavaRecursiveElementWalkingVisitor() {
410 @Override public void visitReferenceExpression(final PsiReferenceExpression expression
) {
411 super.visitReferenceExpression(expression
);
412 final PsiElement element
= expression
.resolve();
413 if (element
instanceof PsiField
) {
415 PsiField field
= (PsiField
)element
;
416 if (myClass
.getContainingClass() != null && field
.getContainingClass() == myClass
.getContainingClass() &&
417 outerClassLocal
!= null) {
418 PsiReferenceExpression expr
= (PsiReferenceExpression
)expression
.copy();
419 PsiExpression qualifier
= myElementFactory
.createExpressionFromText(outerClassLocal
.getName(), field
.getContainingClass());
420 expr
.setQualifierExpression(qualifier
);
421 elementsToReplace
.put(expression
, expr
);
424 catch (IncorrectOperationException e
) {
430 @Override public void visitTypeParameter(final PsiTypeParameter classParameter
) {
431 super.visitTypeParameter(classParameter
);
432 PsiReferenceList list
= classParameter
.getExtendsList();
433 PsiJavaCodeReferenceElement
[] referenceElements
= list
.getReferenceElements();
434 for(PsiJavaCodeReferenceElement reference
: referenceElements
) {
435 PsiElement psiElement
= reference
.resolve();
436 if (psiElement
instanceof PsiTypeParameter
) {
437 checkReplaceTypeParameter(reference
, (PsiTypeParameter
) psiElement
);
442 @Override public void visitTypeElement(final PsiTypeElement typeElement
) {
443 super.visitTypeElement(typeElement
);
444 if (typeElement
.getType() instanceof PsiClassType
) {
445 PsiClassType classType
= (PsiClassType
) typeElement
.getType();
446 PsiClass psiClass
= classType
.resolve();
447 if (psiClass
instanceof PsiTypeParameter
) {
448 checkReplaceTypeParameter(typeElement
, (PsiTypeParameter
) psiClass
);
453 private void checkReplaceTypeParameter(PsiElement element
, PsiTypeParameter target
) {
454 PsiClass containingClass
= method
.getContainingClass();
455 PsiTypeParameter
[] psiTypeParameters
= containingClass
.getTypeParameters();
456 for(int i
=0; i
<psiTypeParameters
.length
; i
++) {
457 if (psiTypeParameters
[i
] == target
) {
458 PsiType substType
= substitutedParameters
[i
];
459 if (substType
== null) {
460 substType
= PsiType
.getJavaLangObject(element
.getManager(), ProjectScope
.getAllScope(element
.getProject()));
462 if (element
instanceof PsiJavaCodeReferenceElement
) {
463 LOG
.assertTrue(substType
instanceof PsiClassType
);
464 elementsToReplace
.put(element
, myElementFactory
.createReferenceElementByType((PsiClassType
)substType
));
466 elementsToReplace
.put(element
, myElementFactory
.createTypeElement(substType
));
472 RefactoringUtil
.replaceElementsWithMap(method
, elementsToReplace
);