Inline inner: assignments to local vars and params in constructor body fixed (IDEA...
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineToAnonymousConstructorProcessor.java
blob7ce79aef8a64118685e378d13cc578cc5b12d5a5
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.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;
39 import java.util.Map;
41 import static com.intellij.patterns.PlatformPatterns.psiElement;
42 import static com.intellij.patterns.PsiJavaPatterns.psiExpressionStatement;
44 /**
45 * @author yole
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) {
72 myClass = aClass;
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);
148 processedFields++;
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() {
170 while(true) {
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();
179 assert body != null;
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;
215 try {
216 initializer = (PsiExpression) replaceParameterReferences((PsiExpression)rExpr.copy(), localVarRefs, false);
218 catch (IncorrectOperationException e) {
219 LOG.error(e);
220 return false;
222 if (!localVarRefs.isEmpty()) {
223 return true;
226 myFieldInitializers.put(field.getName(), initializer);
229 else if (psiElement instanceof PsiVariable) {
230 return true;
233 return false;
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;
252 int index = 0;
253 String localName;
254 while(true) {
255 localName = codeStyleManager.suggestUniqueVariableName(baseNameForIndex, myNewExpression, true);
256 if (myClass.findFieldByName(localName, false) == null) {
257 break;
259 index++;
260 baseNameForIndex = baseName + index;
262 try {
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);
267 return variable;
269 catch (IncorrectOperationException e) {
270 LOG.error(e);
271 return null;
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("[] { }");
286 try {
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) {
298 LOG.error(e);
301 break;
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();
312 assert body != null;
313 PsiStatement[] statements = body.getStatements();
314 if (statements.length == 0) {
315 return;
317 ProcessingContext context = new ProcessingContext();
318 if (!ourSuperCallPattern.accepts(statements[0], context)) {
319 return;
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();
360 try {
361 final PsiExpression replacement = myElementFactory.createExpressionFromText(qualifiedText, myClass);
362 elementsToReplace.put(expression, replacement);
364 catch (IncorrectOperationException e) {
365 LOG.error(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));
388 else {
389 int index = myConstructorParameters.getParameterIndex(param);
390 if (ref == argument) {
391 argument = argument.replace(myConstructorArguments.getExpressions() [index]);
393 else {
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) {
414 try {
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) {
425 LOG.error(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));
465 } else {
466 elementsToReplace.put(element, myElementFactory.createTypeElement(substType));
472 RefactoringUtil.replaceElementsWithMap(method, elementsToReplace);