| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.refactoring.inline; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.patterns.ElementPattern; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; |
| import com.intellij.psi.search.ProjectScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.ProcessingContext; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static com.intellij.patterns.PlatformPatterns.psiElement; |
| import static com.intellij.patterns.PsiJavaPatterns.psiExpressionStatement; |
| |
| /** |
| * @author yole |
| */ |
| class InlineToAnonymousConstructorProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineToAnonymousConstructorProcessor"); |
| |
| private static final Key<PsiAssignmentExpression> ourAssignmentKey = Key.create("assignment"); |
| private static final Key<PsiCallExpression> ourCallKey = Key.create("call"); |
| public static final ElementPattern ourNullPattern = psiElement(PsiLiteralExpression.class).withText(PsiKeyword.NULL); |
| private static final ElementPattern ourAssignmentPattern = psiExpressionStatement().withChild(psiElement(PsiAssignmentExpression.class).save(ourAssignmentKey)); |
| private static final ElementPattern ourSuperCallPattern = psiExpressionStatement().withFirstChild( |
| psiElement(PsiMethodCallExpression.class).save(ourCallKey).withFirstChild(psiElement().withText(PsiKeyword.SUPER))); |
| private static final ElementPattern ourThisCallPattern = psiExpressionStatement().withFirstChild(psiElement(PsiMethodCallExpression.class).withFirstChild( |
| psiElement().withText(PsiKeyword.THIS))); |
| |
| private final PsiClass myClass; |
| private PsiNewExpression myNewExpression; |
| private final PsiType mySuperType; |
| private final Map<String, PsiExpression> myFieldInitializers = new HashMap<String, PsiExpression>(); |
| private final Map<PsiParameter, PsiVariable> myLocalsForParameters = new HashMap<PsiParameter, PsiVariable>(); |
| private PsiStatement myNewStatement; |
| private final PsiElementFactory myElementFactory; |
| private PsiMethod myConstructor; |
| private PsiExpressionList myConstructorArguments; |
| private PsiParameterList myConstructorParameters; |
| |
| public InlineToAnonymousConstructorProcessor(final PsiClass aClass, final PsiNewExpression psiNewExpression, |
| final PsiType superType) { |
| myClass = aClass; |
| myNewExpression = psiNewExpression; |
| mySuperType = superType; |
| myNewStatement = PsiTreeUtil.getParentOfType(myNewExpression, PsiStatement.class); |
| myElementFactory = JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory(); |
| } |
| |
| public void run() throws IncorrectOperationException { |
| checkInlineChainingConstructor(); |
| JavaResolveResult classResolveResult = myNewExpression.getClassReference().advancedResolve(false); |
| JavaResolveResult methodResolveResult = myNewExpression.resolveMethodGenerics(); |
| myConstructor = (PsiMethod) methodResolveResult.getElement(); |
| myConstructorArguments = myNewExpression.getArgumentList(); |
| |
| PsiSubstitutor classResolveSubstitutor = classResolveResult.getSubstitutor(); |
| PsiType substType = classResolveSubstitutor.substitute(mySuperType); |
| |
| PsiTypeParameter[] typeParams = myClass.getTypeParameters(); |
| PsiType[] substitutedParameters = PsiType.createArray(typeParams.length); |
| for(int i=0; i< typeParams.length; i++) { |
| substitutedParameters [i] = classResolveSubstitutor.substitute(typeParams [i]); |
| } |
| |
| @NonNls StringBuilder builder = new StringBuilder("new "); |
| builder.append(substType.getCanonicalText()); |
| builder.append("() {}"); |
| |
| PsiNewExpression superNewExpressionTemplate = (PsiNewExpression) myElementFactory.createExpressionFromText(builder.toString(), |
| myNewExpression.getContainingFile()); |
| PsiClassInitializer initializerBlock = myElementFactory.createClassInitializer(); |
| PsiVariable outerClassLocal = null; |
| if (myNewExpression.getQualifier() != null && myClass.getContainingClass() != null) { |
| outerClassLocal = generateOuterClassLocal(); |
| } |
| if (myConstructor != null) { |
| myConstructorParameters = myConstructor.getParameterList(); |
| |
| final PsiExpressionList argumentList = superNewExpressionTemplate.getArgumentList(); |
| assert argumentList != null; |
| |
| if (myNewStatement != null) { |
| generateLocalsForArguments(); |
| } |
| analyzeConstructor(initializerBlock.getBody()); |
| addSuperConstructorArguments(argumentList); |
| } |
| |
| ChangeContextUtil.encodeContextInfo(myClass.getNavigationElement(), true); |
| PsiClass classCopy = (PsiClass) myClass.getNavigationElement().copy(); |
| ChangeContextUtil.clearContextInfo(myClass); |
| final PsiClass anonymousClass = superNewExpressionTemplate.getAnonymousClass(); |
| assert anonymousClass != null; |
| |
| int fieldCount = myClass.getFields().length; |
| int processedFields = 0; |
| PsiElement token = anonymousClass.getRBrace(); |
| if (initializerBlock.getBody().getStatements().length > 0 && fieldCount == 0) { |
| insertInitializerBefore(initializerBlock, anonymousClass, token); |
| } |
| |
| for(PsiElement child: classCopy.getChildren()) { |
| if ((child instanceof PsiMethod && !((PsiMethod) child).isConstructor()) || |
| child instanceof PsiClassInitializer || child instanceof PsiClass) { |
| if (!myFieldInitializers.isEmpty() || !myLocalsForParameters.isEmpty() || classResolveSubstitutor != PsiSubstitutor.EMPTY || outerClassLocal != null) { |
| replaceReferences((PsiMember) child, substitutedParameters, outerClassLocal); |
| } |
| child = anonymousClass.addBefore(child, token); |
| } |
| else if (child instanceof PsiField) { |
| PsiField field = (PsiField) child; |
| replaceReferences(field, substitutedParameters, outerClassLocal); |
| PsiExpression initializer = myFieldInitializers.get(field.getName()); |
| field = (PsiField) anonymousClass.addBefore(field, token); |
| if (initializer != null) { |
| field.setInitializer(initializer); |
| } |
| processedFields++; |
| if (processedFields == fieldCount && initializerBlock.getBody().getStatements().length > 0) { |
| insertInitializerBefore(initializerBlock, anonymousClass, token); |
| } |
| } |
| } |
| if (PsiTreeUtil.getChildrenOfType(anonymousClass, PsiMember.class) == null) { |
| anonymousClass.deleteChildRange(anonymousClass.getLBrace(), anonymousClass.getRBrace()); |
| } |
| PsiNewExpression superNewExpression = (PsiNewExpression) myNewExpression.replace(superNewExpressionTemplate); |
| superNewExpression = (PsiNewExpression)ChangeContextUtil.decodeContextInfo(superNewExpression, superNewExpression.getAnonymousClass(), null); |
| JavaCodeStyleManager.getInstance(superNewExpression.getProject()).shortenClassReferences(superNewExpression); |
| } |
| |
| private void insertInitializerBefore(final PsiClassInitializer initializerBlock, final PsiClass anonymousClass, final PsiElement token) |
| throws IncorrectOperationException { |
| anonymousClass.addBefore(CodeEditUtil.createLineFeed(token.getManager()), token); |
| anonymousClass.addBefore(initializerBlock, token); |
| anonymousClass.addBefore(CodeEditUtil.createLineFeed(token.getManager()), token); |
| } |
| |
| private void checkInlineChainingConstructor() { |
| while(true) { |
| PsiMethod constructor = myNewExpression.resolveConstructor(); |
| if (constructor == null || !InlineMethodHandler.isChainingConstructor(constructor)) break; |
| InlineMethodProcessor.inlineConstructorCall(myNewExpression); |
| } |
| } |
| |
| private void analyzeConstructor(final PsiCodeBlock initializerBlock) throws IncorrectOperationException { |
| PsiCodeBlock body = myConstructor.getBody(); |
| assert body != null; |
| for(PsiElement child: body.getChildren()) { |
| if (child instanceof PsiStatement) { |
| PsiStatement stmt = (PsiStatement) child; |
| ProcessingContext context = new ProcessingContext(); |
| if (ourAssignmentPattern.accepts(stmt, context)) { |
| PsiAssignmentExpression expression = context.get(ourAssignmentKey); |
| if (processAssignmentInConstructor(expression)) { |
| initializerBlock.addBefore(replaceParameterReferences(stmt, null, false), initializerBlock.getRBrace()); |
| } |
| } |
| else if (!ourSuperCallPattern.accepts(stmt) && !ourThisCallPattern.accepts(stmt)) { |
| replaceParameterReferences(stmt, new ArrayList<PsiReferenceExpression>(), false); |
| initializerBlock.addBefore(stmt, initializerBlock.getRBrace()); |
| } |
| } |
| else if (child instanceof PsiComment) { |
| if (child.getPrevSibling() instanceof PsiWhiteSpace) { |
| initializerBlock.addBefore(child.getPrevSibling(), initializerBlock.getRBrace()); |
| } |
| initializerBlock.addBefore(child, initializerBlock.getRBrace()); |
| } |
| } |
| } |
| |
| private boolean processAssignmentInConstructor(final PsiAssignmentExpression expression) { |
| if (expression.getLExpression() instanceof PsiReferenceExpression) { |
| PsiReferenceExpression lExpr = (PsiReferenceExpression) expression.getLExpression(); |
| final PsiExpression rExpr = expression.getRExpression(); |
| if (rExpr == null) return false; |
| final PsiElement psiElement = lExpr.resolve(); |
| if (psiElement instanceof PsiField) { |
| PsiField field = (PsiField) psiElement; |
| if (myClass.getManager().areElementsEquivalent(field.getContainingClass(), myClass)) { |
| final List<PsiReferenceExpression> localVarRefs = new ArrayList<PsiReferenceExpression>(); |
| final PsiExpression initializer; |
| try { |
| initializer = (PsiExpression) replaceParameterReferences(rExpr.copy(), localVarRefs, false); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| return false; |
| } |
| if (!localVarRefs.isEmpty()) { |
| return true; |
| } |
| |
| myFieldInitializers.put(field.getName(), initializer); |
| } |
| } |
| else if (psiElement instanceof PsiVariable) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isConstant(final PsiExpression expr) { |
| Object constantValue = JavaPsiFacade.getInstance(expr.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr); |
| return constantValue != null || ourNullPattern.accepts(expr); |
| } |
| |
| private PsiVariable generateOuterClassLocal() { |
| PsiClass outerClass = myClass.getContainingClass(); |
| assert outerClass != null; |
| return generateLocal(StringUtil.decapitalize(outerClass.getName()), |
| myElementFactory.createType(outerClass), myNewExpression.getQualifier()); |
| } |
| |
| private PsiVariable generateLocal(final String baseName, final PsiType type, final PsiExpression initializer) { |
| final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myClass.getProject()); |
| |
| String baseNameForIndex = baseName; |
| int index = 0; |
| String localName; |
| while(true) { |
| localName = codeStyleManager.suggestUniqueVariableName(baseNameForIndex, myNewExpression, true); |
| if (myClass.findFieldByName(localName, false) == null) { |
| break; |
| } |
| index++; |
| baseNameForIndex = baseName + index; |
| } |
| try { |
| final PsiDeclarationStatement declaration = myElementFactory.createVariableDeclarationStatement(localName, type, initializer); |
| PsiVariable variable = (PsiVariable)declaration.getDeclaredElements()[0]; |
| PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true); |
| final PsiElement parent = myNewStatement.getParent(); |
| if (parent instanceof PsiCodeBlock) { |
| variable = (PsiVariable)((PsiDeclarationStatement)parent.addBefore(declaration, myNewStatement)).getDeclaredElements()[0]; |
| } |
| else { |
| final int offsetInStatement = myNewExpression.getTextRange().getStartOffset() - myNewStatement.getTextRange().getStartOffset(); |
| final PsiBlockStatement blockStatement = (PsiBlockStatement)myElementFactory.createStatementFromText("{}", null); |
| PsiCodeBlock block = blockStatement.getCodeBlock(); |
| block.add(declaration); |
| block.add(myNewStatement); |
| block = ((PsiBlockStatement)myNewStatement.replace(blockStatement)).getCodeBlock(); |
| |
| variable = (PsiVariable)((PsiDeclarationStatement)block.getStatements()[0]).getDeclaredElements()[0]; |
| myNewStatement = block.getStatements()[1]; |
| myNewExpression = PsiTreeUtil.getParentOfType(myNewStatement.findElementAt(offsetInStatement), PsiNewExpression.class); |
| } |
| |
| return variable; |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| |
| private void generateLocalsForArguments() { |
| PsiExpression[] expressions = myConstructorArguments.getExpressions(); |
| for (int i = 0; i < expressions.length; i++) { |
| PsiExpression expr = expressions[i]; |
| PsiParameter parameter = myConstructorParameters.getParameters()[i]; |
| if (parameter.isVarArgs()) { |
| PsiEllipsisType ellipsisType = (PsiEllipsisType)parameter.getType(); |
| PsiType baseType = ellipsisType.getComponentType(); |
| @NonNls StringBuilder exprBuilder = new StringBuilder("new "); |
| exprBuilder.append(baseType.getCanonicalText()); |
| exprBuilder.append("[] { }"); |
| try { |
| PsiNewExpression newExpr = (PsiNewExpression) myElementFactory.createExpressionFromText(exprBuilder.toString(), myClass); |
| PsiArrayInitializerExpression arrayInitializer = newExpr.getArrayInitializer(); |
| assert arrayInitializer != null; |
| for(int j=i; j < expressions.length; j++) { |
| arrayInitializer.add(expressions [j]); |
| } |
| |
| PsiVariable variable = generateLocal(parameter.getName(), ellipsisType.toArrayType(), newExpr); |
| myLocalsForParameters.put(parameter, variable); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| |
| break; |
| } |
| else if (!isConstant(expr)) { |
| PsiVariable variable = generateLocal(parameter.getName(), parameter.getType(), expr); |
| myLocalsForParameters.put(parameter, variable); |
| } |
| } |
| } |
| |
| private void addSuperConstructorArguments(PsiExpressionList argumentList) throws IncorrectOperationException { |
| final PsiCodeBlock body = myConstructor.getBody(); |
| assert body != null; |
| PsiStatement[] statements = body.getStatements(); |
| if (statements.length == 0) { |
| return; |
| } |
| ProcessingContext context = new ProcessingContext(); |
| if (!ourSuperCallPattern.accepts(statements[0], context)) { |
| return; |
| } |
| PsiExpressionList superArguments = context.get(ourCallKey).getArgumentList(); |
| if (superArguments != null) { |
| for(PsiExpression argument: superArguments.getExpressions()) { |
| final PsiElement superArgument = replaceParameterReferences(argument.copy(), new ArrayList<PsiReferenceExpression>(), true); |
| argumentList.add(superArgument); |
| } |
| } |
| } |
| |
| private PsiElement replaceParameterReferences(PsiElement argument, |
| @Nullable final List<PsiReferenceExpression> localVarRefs, |
| final boolean replaceFieldsWithInitializers) throws IncorrectOperationException { |
| if (argument instanceof PsiReferenceExpression) { |
| PsiElement element = ((PsiReferenceExpression)argument).resolve(); |
| if (element instanceof PsiParameter) { |
| PsiParameter parameter = (PsiParameter)element; |
| if (myLocalsForParameters.containsKey(parameter)) { |
| return argument.replace(getParameterReference(parameter)); |
| } |
| int index = myConstructorParameters.getParameterIndex(parameter); |
| return argument.replace(myConstructorArguments.getExpressions() [index]); |
| } |
| } |
| |
| final List<Pair<PsiReferenceExpression, PsiParameter>> parameterReferences = new ArrayList<Pair<PsiReferenceExpression, PsiParameter>>(); |
| final Map<PsiElement, PsiElement> elementsToReplace = new HashMap<PsiElement, PsiElement>(); |
| argument.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override public void visitReferenceExpression(final PsiReferenceExpression expression) { |
| super.visitReferenceExpression(expression); |
| final PsiElement psiElement = expression.resolve(); |
| if (psiElement instanceof PsiParameter) { |
| parameterReferences.add(Pair.create(expression, (PsiParameter)psiElement)); |
| } |
| else if ((psiElement instanceof PsiField || psiElement instanceof PsiMethod) && |
| ((PsiMember) psiElement).getContainingClass() == myClass.getSuperClass()) { |
| PsiMember member = (PsiMember) psiElement; |
| if (member.hasModifierProperty(PsiModifier.STATIC) && |
| expression.getQualifierExpression() == null) { |
| final String qualifiedText = myClass.getSuperClass().getQualifiedName() + "." + member.getName(); |
| try { |
| final PsiExpression replacement = myElementFactory.createExpressionFromText(qualifiedText, myClass); |
| elementsToReplace.put(expression, replacement); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| else if (psiElement instanceof PsiVariable) { |
| if (localVarRefs != null) { |
| localVarRefs.add(expression); |
| } |
| if (replaceFieldsWithInitializers && psiElement instanceof PsiField && ((PsiField) psiElement).getContainingClass() == myClass) { |
| final PsiExpression initializer = ((PsiField)psiElement).getInitializer(); |
| if (isConstant(initializer)) { |
| elementsToReplace.put(expression, initializer); |
| } |
| } |
| } |
| } |
| }); |
| for (Pair<PsiReferenceExpression, PsiParameter> pair: parameterReferences) { |
| PsiReferenceExpression ref = pair.first; |
| PsiParameter param = pair.second; |
| if (myLocalsForParameters.containsKey(param)) { |
| ref.replace(getParameterReference(param)); |
| } |
| else { |
| int index = myConstructorParameters.getParameterIndex(param); |
| if (ref == argument) { |
| argument = argument.replace(myConstructorArguments.getExpressions() [index]); |
| } |
| else { |
| ref.replace(myConstructorArguments.getExpressions() [index]); |
| } |
| } |
| } |
| return RefactoringUtil.replaceElementsWithMap(argument, elementsToReplace); |
| } |
| |
| private PsiExpression getParameterReference(final PsiParameter parameter) throws IncorrectOperationException { |
| PsiVariable variable = myLocalsForParameters.get(parameter); |
| return myElementFactory.createExpressionFromText(variable.getName(), myClass); |
| } |
| |
| private void replaceReferences(final PsiMember method, |
| final PsiType[] substitutedParameters, final PsiVariable outerClassLocal) throws IncorrectOperationException { |
| final Map<PsiElement, PsiElement> elementsToReplace = new HashMap<PsiElement, PsiElement>(); |
| method.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override public void visitReferenceExpression(final PsiReferenceExpression expression) { |
| super.visitReferenceExpression(expression); |
| final PsiElement element = expression.resolve(); |
| if (element instanceof PsiField) { |
| try { |
| PsiField field = (PsiField)element; |
| if (myClass.getContainingClass() != null && field.getContainingClass() == myClass.getContainingClass() && |
| outerClassLocal != null) { |
| PsiReferenceExpression expr = (PsiReferenceExpression)expression.copy(); |
| PsiExpression qualifier = myElementFactory.createExpressionFromText(outerClassLocal.getName(), field.getContainingClass()); |
| expr.setQualifierExpression(qualifier); |
| elementsToReplace.put(expression, expr); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| |
| @Override public void visitTypeParameter(final PsiTypeParameter classParameter) { |
| super.visitTypeParameter(classParameter); |
| PsiReferenceList list = classParameter.getExtendsList(); |
| PsiJavaCodeReferenceElement[] referenceElements = list.getReferenceElements(); |
| for(PsiJavaCodeReferenceElement reference: referenceElements) { |
| PsiElement psiElement = reference.resolve(); |
| if (psiElement instanceof PsiTypeParameter) { |
| checkReplaceTypeParameter(reference, (PsiTypeParameter) psiElement); |
| } |
| } |
| } |
| |
| @Override public void visitTypeElement(final PsiTypeElement typeElement) { |
| super.visitTypeElement(typeElement); |
| if (typeElement.getType() instanceof PsiClassType) { |
| PsiClassType classType = (PsiClassType) typeElement.getType(); |
| PsiClass psiClass = classType.resolve(); |
| if (psiClass instanceof PsiTypeParameter) { |
| checkReplaceTypeParameter(typeElement, (PsiTypeParameter) psiClass); |
| } |
| } |
| } |
| |
| private void checkReplaceTypeParameter(PsiElement element, PsiTypeParameter target) { |
| PsiClass containingClass = method.getContainingClass(); |
| PsiTypeParameter[] psiTypeParameters = containingClass.getTypeParameters(); |
| for(int i=0; i<psiTypeParameters.length; i++) { |
| if (psiTypeParameters [i] == target) { |
| PsiType substType = substitutedParameters[i]; |
| if (substType == null) { |
| substType = PsiType.getJavaLangObject(element.getManager(), ProjectScope.getAllScope(element.getProject())); |
| } |
| if (element instanceof PsiJavaCodeReferenceElement) { |
| LOG.assertTrue(substType instanceof PsiClassType); |
| elementsToReplace.put(element, myElementFactory.createReferenceElementByType((PsiClassType)substType)); |
| } else { |
| elementsToReplace.put(element, myElementFactory.createTypeElement(substType)); |
| } |
| } |
| } |
| } |
| }); |
| RefactoringUtil.replaceElementsWithMap(method, elementsToReplace); |
| } |
| } |