| /* |
| * Copyright 2000-2013 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.history.LocalHistory; |
| import com.intellij.history.LocalHistoryAction; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.findUsages.DescriptiveNameUtil; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.lang.refactoring.InlineHandler; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.controlFlow.*; |
| import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; |
| import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef; |
| import com.intellij.psi.infos.MethodCandidateInfo; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiTypesUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.refactoring.BaseRefactoringProcessor; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.introduceParameter.Util; |
| import com.intellij.refactoring.listeners.RefactoringEventData; |
| import com.intellij.refactoring.rename.NonCodeUsageInfoFactory; |
| import com.intellij.refactoring.rename.RenameJavaVariableProcessor; |
| import com.intellij.refactoring.util.*; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class InlineMethodProcessor extends BaseRefactoringProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineMethodProcessor"); |
| |
| private PsiMethod myMethod; |
| private PsiJavaCodeReferenceElement myReference; |
| private final Editor myEditor; |
| private final boolean myInlineThisOnly; |
| private final boolean mySearchInComments; |
| private final boolean mySearchForTextOccurrences; |
| |
| private final PsiManager myManager; |
| private final PsiElementFactory myFactory; |
| private final CodeStyleManager myCodeStyleManager; |
| private final JavaCodeStyleManager myJavaCodeStyle; |
| |
| private PsiCodeBlock[] myAddedBraces; |
| private final String myDescriptiveName; |
| private Map<PsiField, PsiClassInitializer> myAddedClassInitializers; |
| private PsiMethod myMethodCopy; |
| private Map<Language,InlineHandler.Inliner> myInliners; |
| |
| public InlineMethodProcessor(@NotNull Project project, |
| @NotNull PsiMethod method, |
| @Nullable PsiJavaCodeReferenceElement reference, |
| Editor editor, |
| boolean isInlineThisOnly) { |
| this(project, method, reference, editor, isInlineThisOnly, false, false); |
| } |
| |
| public InlineMethodProcessor(@NotNull Project project, |
| @NotNull PsiMethod method, |
| @Nullable PsiJavaCodeReferenceElement reference, |
| Editor editor, |
| boolean isInlineThisOnly, |
| boolean searchInComments, |
| boolean searchForTextOccurrences) { |
| super(project); |
| myMethod = method; |
| myReference = reference; |
| myEditor = editor; |
| myInlineThisOnly = isInlineThisOnly; |
| mySearchInComments = searchInComments; |
| mySearchForTextOccurrences = searchForTextOccurrences; |
| |
| myManager = PsiManager.getInstance(myProject); |
| myFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); |
| myCodeStyleManager = CodeStyleManager.getInstance(myProject); |
| myJavaCodeStyle = JavaCodeStyleManager.getInstance(myProject); |
| myDescriptiveName = DescriptiveNameUtil.getDescriptiveName(myMethod); |
| } |
| |
| protected String getCommandName() { |
| return RefactoringBundle.message("inline.method.command", myDescriptiveName); |
| } |
| |
| @NotNull |
| protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { |
| return new InlineViewDescriptor(myMethod); |
| } |
| |
| @NotNull |
| protected UsageInfo[] findUsages() { |
| if (myInlineThisOnly) return new UsageInfo[]{new UsageInfo(myReference)}; |
| Set<UsageInfo> usages = new HashSet<UsageInfo>(); |
| if (myReference != null) { |
| usages.add(new UsageInfo(myReference)); |
| } |
| for (PsiReference reference : ReferencesSearch.search(myMethod)) { |
| usages.add(new UsageInfo(reference.getElement())); |
| } |
| |
| if (mySearchInComments || mySearchForTextOccurrences) { |
| final NonCodeUsageInfoFactory infoFactory = new NonCodeUsageInfoFactory(myMethod, myMethod.getName()) { |
| @Override |
| public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) { |
| if (PsiTreeUtil.isAncestor(myMethod, usage, false)) return null; |
| return super.createUsageInfo(usage, startOffset, endOffset); |
| } |
| }; |
| if (mySearchInComments) { |
| String stringToSearch = ElementDescriptionUtil.getElementDescription(myMethod, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS); |
| TextOccurrencesUtil.addUsagesInStringsAndComments(myMethod, stringToSearch, usages, infoFactory); |
| } |
| |
| if (mySearchForTextOccurrences) { |
| String stringToSearch = ElementDescriptionUtil.getElementDescription(myMethod, NonCodeSearchDescriptionLocation.NON_JAVA); |
| TextOccurrencesUtil |
| .addTextOccurences(myMethod, stringToSearch, GlobalSearchScope.projectScope(myProject), usages, infoFactory); |
| } |
| } |
| |
| return usages.toArray(new UsageInfo[usages.size()]); |
| } |
| |
| @Override |
| protected boolean isPreviewUsages(UsageInfo[] usages) { |
| for (UsageInfo usage : usages) { |
| if (usage instanceof NonCodeUsageInfo) return true; |
| } |
| return super.isPreviewUsages(usages); |
| } |
| |
| protected void refreshElements(PsiElement[] elements) { |
| boolean condition = elements.length == 1 && elements[0] instanceof PsiMethod; |
| LOG.assertTrue(condition); |
| myMethod = (PsiMethod)elements[0]; |
| } |
| |
| protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) { |
| if (!myInlineThisOnly && checkReadOnly()) { |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, myMethod)) return false; |
| } |
| final UsageInfo[] usagesIn = refUsages.get(); |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| |
| if (!myInlineThisOnly) { |
| final PsiMethod[] superMethods = myMethod.findSuperMethods(); |
| for (PsiMethod method : superMethods) { |
| final String message = method.hasModifierProperty(PsiModifier.ABSTRACT) ? RefactoringBundle |
| .message("inlined.method.implements.method.from.0", method.getContainingClass().getQualifiedName()) : RefactoringBundle |
| .message("inlined.method.overrides.method.from.0", method.getContainingClass().getQualifiedName()); |
| conflicts.putValue(method, message); |
| } |
| |
| for (UsageInfo info : usagesIn) { |
| final PsiElement element = info.getElement(); |
| if (element instanceof PsiDocMethodOrFieldRef && !PsiTreeUtil.isAncestor(myMethod, element, false)) { |
| conflicts.putValue(element, "Inlined method is used in javadoc"); |
| } |
| if (element instanceof PsiMethodReferenceExpression) { |
| conflicts.putValue(element, "Inlined method is used in method reference"); |
| } |
| |
| final String errorMessage = checkCalledInSuperOrThisExpr(myMethod.getBody(), element); |
| if (errorMessage != null) { |
| conflicts.putValue(element, errorMessage); |
| } |
| } |
| } |
| |
| ArrayList<PsiReference> refs = convertUsagesToRefs(usagesIn); |
| myInliners = GenericInlineHandler.initializeInliners(myMethod, new InlineHandler.Settings() { |
| @Override |
| public boolean isOnlyOneReferenceToInline() { |
| return myInlineThisOnly; |
| } |
| }, refs); |
| |
| //hack to prevent conflicts 'Cannot inline reference from Java' |
| myInliners.put(JavaLanguage.INSTANCE, new InlineHandler.Inliner() { |
| @Nullable |
| @Override |
| public MultiMap<PsiElement, String> getConflicts(PsiReference reference, PsiElement referenced) { |
| return MultiMap.emptyInstance(); |
| } |
| |
| @Override |
| public void inlineUsage(@NotNull UsageInfo usage, @NotNull PsiElement referenced) { |
| if (usage instanceof NonCodeUsageInfo) return; |
| |
| throw new UnsupportedOperationException( |
| "usage: " + usage.getClass().getName() + ", referenced: " + referenced.getClass().getName() + "text: " + referenced.getText()); |
| } |
| }); |
| |
| for (PsiReference ref : refs) { |
| GenericInlineHandler.collectConflicts(ref, myMethod, myInliners, conflicts); |
| } |
| |
| final PsiReturnStatement[] returnStatements = PsiUtil.findReturnStatements(myMethod); |
| for (PsiReturnStatement statement : returnStatements) { |
| PsiExpression value = statement.getReturnValue(); |
| if (value != null && !(value instanceof PsiCallExpression)) { |
| for (UsageInfo info : usagesIn) { |
| PsiReference reference = info.getReference(); |
| if (reference != null) { |
| InlineUtil.TailCallType type = InlineUtil.getTailCallType(reference); |
| if (type == InlineUtil.TailCallType.Simple) { |
| conflicts.putValue(statement, "Inlined result would contain parse errors"); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| addInaccessibleMemberConflicts(myMethod, usagesIn, new ReferencedElementsCollector(), conflicts); |
| |
| addInaccessibleSuperCallsConflicts(usagesIn, conflicts); |
| |
| return showConflicts(conflicts, usagesIn); |
| } |
| |
| private static ArrayList<PsiReference> convertUsagesToRefs(UsageInfo[] usagesIn) { |
| ArrayList<PsiReference> refs = new ArrayList<PsiReference>(); |
| for (UsageInfo info : usagesIn) { |
| final PsiReference ref = info.getReference(); |
| if (ref != null) { //ref can be null if it is conflict usage info |
| refs.add(ref); |
| } |
| } |
| return refs; |
| } |
| |
| private boolean checkReadOnly() { |
| return myMethod.isWritable() || myMethod instanceof PsiCompiledElement; |
| } |
| |
| private void addInaccessibleSuperCallsConflicts(final UsageInfo[] usagesIn, final MultiMap<PsiElement, String> conflicts) { |
| |
| myMethod.accept(new JavaRecursiveElementWalkingVisitor(){ |
| @Override |
| public void visitClass(PsiClass aClass) {} |
| |
| @Override |
| public void visitAnonymousClass(PsiAnonymousClass aClass) {} |
| |
| @Override |
| public void visitSuperExpression(PsiSuperExpression expression) { |
| super.visitSuperExpression(expression); |
| final PsiType type = expression.getType(); |
| final PsiClass superClass = PsiUtil.resolveClassInType(type); |
| if (superClass != null) { |
| final Set<PsiClass> targetContainingClasses = new HashSet<PsiClass>(); |
| for (UsageInfo info : usagesIn) { |
| final PsiElement element = info.getElement(); |
| if (element != null) { |
| final PsiClass targetContainingClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); |
| if (targetContainingClass != null && !InheritanceUtil.isInheritorOrSelf(targetContainingClass, superClass, true)) { |
| targetContainingClasses.add(targetContainingClass); |
| } |
| } |
| } |
| if (!targetContainingClasses.isEmpty()) { |
| final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class); |
| LOG.assertTrue(methodCallExpression != null); |
| conflicts.putValue(expression, "Inlined method calls " + methodCallExpression.getText() + " which won't be accessed in " + |
| StringUtil.join(targetContainingClasses, new Function<PsiClass, String>() { |
| public String fun(PsiClass psiClass) { |
| return RefactoringUIUtil.getDescription(psiClass, false); |
| } |
| }, ",")); |
| } |
| } |
| } |
| }); |
| } |
| |
| public static void addInaccessibleMemberConflicts(final PsiElement element, |
| final UsageInfo[] usages, |
| final ReferencedElementsCollector collector, |
| final MultiMap<PsiElement, String> conflicts) { |
| element.accept(collector); |
| final Map<PsiMember, Set<PsiMember>> containersToReferenced = getInaccessible(collector.myReferencedMembers, usages, element); |
| |
| final Set<PsiMember> containers = containersToReferenced.keySet(); |
| for (PsiMember container : containers) { |
| Set<PsiMember> referencedInaccessible = containersToReferenced.get(container); |
| for (PsiMember referenced : referencedInaccessible) { |
| final String referencedDescription = RefactoringUIUtil.getDescription(referenced, true); |
| final String containerDescription = RefactoringUIUtil.getDescription(container, true); |
| String message = RefactoringBundle.message("0.that.is.used.in.inlined.method.is.not.accessible.from.call.site.s.in.1", |
| referencedDescription, containerDescription); |
| conflicts.putValue(container, CommonRefactoringUtil.capitalize(message)); |
| } |
| } |
| } |
| |
| /** |
| * Given a set of referencedElements, returns a map from containers (in a sense of ConflictsUtil.getContainer) |
| * to subsets of referencedElemens that are not accessible from that container |
| * |
| * @param referencedElements |
| * @param usages |
| * @param elementToInline |
| */ |
| private static Map<PsiMember, Set<PsiMember>> getInaccessible(HashSet<PsiMember> referencedElements, |
| UsageInfo[] usages, |
| PsiElement elementToInline) { |
| Map<PsiMember, Set<PsiMember>> result = new HashMap<PsiMember, Set<PsiMember>>(); |
| |
| for (UsageInfo usage : usages) { |
| final PsiElement usageElement = usage.getElement(); |
| if (usageElement == null) continue; |
| final PsiElement container = ConflictsUtil.getContainer(usageElement); |
| if (!(container instanceof PsiMember)) continue; // usage in import statement |
| PsiMember memberContainer = (PsiMember)container; |
| Set<PsiMember> inaccessibleReferenced = result.get(memberContainer); |
| if (inaccessibleReferenced == null) { |
| inaccessibleReferenced = new HashSet<PsiMember>(); |
| result.put(memberContainer, inaccessibleReferenced); |
| for (PsiMember member : referencedElements) { |
| if (PsiTreeUtil.isAncestor(elementToInline, member, false)) continue; |
| if (elementToInline instanceof PsiClass && |
| InheritanceUtil.isInheritorOrSelf((PsiClass)elementToInline, member.getContainingClass(), true)) continue; |
| if (!PsiUtil.isAccessible(usage.getProject(), member, usageElement, null)) { |
| inaccessibleReferenced.add(member); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| protected void performRefactoring(UsageInfo[] usages) { |
| int col = -1; |
| int line = -1; |
| if (myEditor != null) { |
| col = myEditor.getCaretModel().getLogicalPosition().column; |
| line = myEditor.getCaretModel().getLogicalPosition().line; |
| LogicalPosition pos = new LogicalPosition(0, 0); |
| myEditor.getCaretModel().moveToLogicalPosition(pos); |
| } |
| |
| LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName()); |
| try { |
| doRefactoring(usages); |
| } |
| finally { |
| a.finish(); |
| } |
| |
| if (myEditor != null) { |
| LogicalPosition pos = new LogicalPosition(line, col); |
| myEditor.getCaretModel().moveToLogicalPosition(pos); |
| } |
| } |
| |
| @Nullable |
| @Override |
| protected String getRefactoringId() { |
| return "refactoring.inline.method"; |
| } |
| |
| @Nullable |
| @Override |
| protected RefactoringEventData getBeforeData() { |
| final RefactoringEventData data = new RefactoringEventData(); |
| data.addElement(myMethod); |
| return data; |
| } |
| |
| private void doRefactoring(UsageInfo[] usages) { |
| try { |
| if (myInlineThisOnly) { |
| if (myMethod.isConstructor() && InlineMethodHandler.isChainingConstructor(myMethod)) { |
| PsiCall constructorCall = RefactoringUtil.getEnclosingConstructorCall(myReference); |
| if (constructorCall != null) { |
| inlineConstructorCall(constructorCall); |
| } |
| } |
| else { |
| myReference = addBracesWhenNeeded(new PsiReferenceExpression[]{(PsiReferenceExpression)myReference})[0]; |
| inlineMethodCall((PsiReferenceExpression)myReference); |
| } |
| } |
| else { |
| CommonRefactoringUtil.sortDepthFirstRightLeftOrder(usages); |
| if (myMethod.isConstructor()) { |
| for (UsageInfo usage : usages) { |
| PsiElement element = usage.getElement(); |
| if (element instanceof PsiJavaCodeReferenceElement) { |
| PsiCall constructorCall = RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)element); |
| if (constructorCall != null) { |
| inlineConstructorCall(constructorCall); |
| } |
| } |
| else if (element instanceof PsiEnumConstant) { |
| inlineConstructorCall((PsiEnumConstant) element); |
| } |
| else { |
| GenericInlineHandler.inlineReference(usage, myMethod, myInliners); |
| } |
| } |
| myMethod.delete(); |
| } |
| else { |
| List<PsiReferenceExpression> refExprList = new ArrayList<PsiReferenceExpression>(); |
| final List<PsiElement> imports2Delete = new ArrayList<PsiElement>(); |
| for (final UsageInfo usage : usages) { |
| final PsiElement element = usage.getElement(); |
| if (element instanceof PsiReferenceExpression) { |
| refExprList.add((PsiReferenceExpression)element); |
| } else if (element instanceof PsiImportStaticReferenceElement) { |
| final JavaResolveResult[] resolveResults = ((PsiImportStaticReferenceElement)element).multiResolve(false); |
| if (resolveResults.length < 2) { |
| //no overloads available: ensure broken import are deleted and |
| //unused overloaded imports are deleted by optimize imports helper |
| imports2Delete.add(PsiTreeUtil.getParentOfType(element, PsiImportStaticStatement.class)); |
| } |
| } |
| else if (JavaLanguage.INSTANCE != element.getLanguage()) { |
| GenericInlineHandler.inlineReference(usage, myMethod, myInliners); |
| } |
| } |
| PsiReferenceExpression[] refs = refExprList.toArray(new PsiReferenceExpression[refExprList.size()]); |
| refs = addBracesWhenNeeded(refs); |
| for (PsiReferenceExpression ref : refs) { |
| if (ref instanceof PsiMethodReferenceExpression) continue; |
| inlineMethodCall(ref); |
| } |
| for (PsiElement psiElement : imports2Delete) { |
| if (psiElement != null && psiElement.isValid()) { |
| psiElement.delete(); |
| } |
| } |
| if (myMethod.isWritable()) myMethod.delete(); |
| } |
| } |
| removeAddedBracesWhenPossible(); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| public static void inlineConstructorCall(PsiCall constructorCall) { |
| final PsiMethod oldConstructor = constructorCall.resolveMethod(); |
| LOG.assertTrue(oldConstructor != null); |
| PsiExpression[] instanceCreationArguments = constructorCall.getArgumentList().getExpressions(); |
| if (oldConstructor.isVarArgs()) { //wrap with explicit array |
| final PsiParameter[] parameters = oldConstructor.getParameterList().getParameters(); |
| final PsiType varargType = parameters[parameters.length - 1].getType(); |
| if (varargType instanceof PsiEllipsisType) { |
| final PsiType arrayType = |
| constructorCall.resolveMethodGenerics().getSubstitutor().substitute(((PsiEllipsisType)varargType).getComponentType()); |
| final PsiExpression[] exprs = new PsiExpression[parameters.length]; |
| System.arraycopy(instanceCreationArguments, 0, exprs, 0, parameters.length - 1); |
| StringBuilder varargs = new StringBuilder(); |
| for (int i = parameters.length - 1; i < instanceCreationArguments.length; i++) { |
| if (varargs.length() > 0) varargs.append(", "); |
| varargs.append(instanceCreationArguments[i].getText()); |
| } |
| |
| exprs[parameters.length - 1] = JavaPsiFacade.getElementFactory(constructorCall.getProject()) |
| .createExpressionFromText("new " + arrayType.getCanonicalText() + "[]{" + varargs.toString() + "}", constructorCall); |
| |
| instanceCreationArguments = exprs; |
| } |
| } |
| |
| PsiStatement[] statements = oldConstructor.getBody().getStatements(); |
| LOG.assertTrue(statements.length == 1 && statements[0] instanceof PsiExpressionStatement); |
| PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); |
| LOG.assertTrue(expression instanceof PsiMethodCallExpression); |
| ChangeContextUtil.encodeContextInfo(expression, true); |
| |
| PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression.copy(); |
| final PsiExpression[] args = methodCall.getArgumentList().getExpressions(); |
| for (PsiExpression arg : args) { |
| replaceParameterReferences(arg, oldConstructor, instanceCreationArguments); |
| } |
| |
| try { |
| final PsiExpressionList exprList = (PsiExpressionList) constructorCall.getArgumentList().replace(methodCall.getArgumentList()); |
| ChangeContextUtil.decodeContextInfo(exprList, PsiTreeUtil.getParentOfType(constructorCall, PsiClass.class), null); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| ChangeContextUtil.clearContextInfo(expression); |
| } |
| |
| private static void replaceParameterReferences(final PsiElement element, |
| final PsiMethod oldConstructor, |
| final PsiExpression[] instanceCreationArguments) { |
| boolean isParameterReference = false; |
| if (element instanceof PsiReferenceExpression) { |
| final PsiReferenceExpression expression = (PsiReferenceExpression)element; |
| PsiElement resolved = expression.resolve(); |
| if (resolved instanceof PsiParameter && |
| element.getManager().areElementsEquivalent(((PsiParameter)resolved).getDeclarationScope(), oldConstructor)) { |
| isParameterReference = true; |
| PsiElement declarationScope = ((PsiParameter)resolved).getDeclarationScope(); |
| PsiParameter[] declarationParameters = ((PsiMethod)declarationScope).getParameterList().getParameters(); |
| for (int j = 0; j < declarationParameters.length; j++) { |
| if (declarationParameters[j] == resolved) { |
| try { |
| expression.replace(instanceCreationArguments[j]); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| } |
| } |
| if (!isParameterReference) { |
| PsiElement child = element.getFirstChild(); |
| while (child != null) { |
| PsiElement next = child.getNextSibling(); |
| replaceParameterReferences(child, oldConstructor, instanceCreationArguments); |
| child = next; |
| } |
| } |
| } |
| |
| public void inlineMethodCall(PsiReferenceExpression ref) throws IncorrectOperationException { |
| InlineUtil.TailCallType tailCall = InlineUtil.getTailCallType(ref); |
| ChangeContextUtil.encodeContextInfo(myMethod, false); |
| myMethodCopy = (PsiMethod)myMethod.copy(); |
| ChangeContextUtil.clearContextInfo(myMethod); |
| |
| PsiMethodCallExpression methodCall = (PsiMethodCallExpression)ref.getParent(); |
| |
| PsiSubstitutor callSubstitutor = getCallSubstitutor(methodCall); |
| BlockData blockData = prepareBlock(ref, callSubstitutor, methodCall.getArgumentList(), tailCall); |
| solveVariableNameConflicts(blockData.block, ref); |
| if (callSubstitutor != PsiSubstitutor.EMPTY) { |
| substituteMethodTypeParams(blockData.block, callSubstitutor); |
| } |
| addParmAndThisVarInitializers(blockData, methodCall); |
| |
| PsiElement anchor = RefactoringUtil.getParentStatement(methodCall, true); |
| if (anchor == null) { |
| PsiEnumConstant enumConstant = PsiTreeUtil.getParentOfType(methodCall, PsiEnumConstant.class); |
| if (enumConstant != null) { |
| PsiExpression returnExpr = getSimpleReturnedExpression(myMethod); |
| if (returnExpr != null) { |
| methodCall.replace(returnExpr); |
| } |
| } |
| return; |
| } |
| PsiElement anchorParent = anchor.getParent(); |
| PsiLocalVariable thisVar = null; |
| PsiLocalVariable[] parmVars = new PsiLocalVariable[blockData.parmVars.length]; |
| PsiLocalVariable resultVar = null; |
| PsiStatement[] statements = blockData.block.getStatements(); |
| if (statements.length > 0) { |
| int last = statements.length - 1; |
| /*PsiElement first = statements[0]; |
| PsiElement last = statements[statements.length - 1];*/ |
| |
| if (statements.length > 0 && statements[statements.length - 1] instanceof PsiReturnStatement && |
| tailCall != InlineUtil.TailCallType.Return) { |
| last--; |
| } |
| |
| int first = 0; |
| if (first <= last) { |
| final PsiElement rBraceOrReturnStatement = |
| PsiTreeUtil.skipSiblingsForward(statements[last], PsiWhiteSpace.class, PsiComment.class); |
| LOG.assertTrue(rBraceOrReturnStatement != null); |
| final PsiElement beforeRBraceStatement = rBraceOrReturnStatement.getPrevSibling(); |
| LOG.assertTrue(beforeRBraceStatement != null); |
| PsiElement firstAdded = anchorParent.addRangeBefore(statements[first], beforeRBraceStatement, anchor); |
| |
| PsiElement current = firstAdded.getPrevSibling(); |
| LOG.assertTrue(current != null); |
| if (blockData.thisVar != null) { |
| PsiDeclarationStatement statement = PsiTreeUtil.getNextSiblingOfType(current, PsiDeclarationStatement.class); |
| thisVar = (PsiLocalVariable)statement.getDeclaredElements()[0]; |
| current = statement; |
| } |
| for (int i = 0; i < parmVars.length; i++) { |
| PsiDeclarationStatement statement = PsiTreeUtil.getNextSiblingOfType(current, PsiDeclarationStatement.class); |
| parmVars[i] = (PsiLocalVariable)statement.getDeclaredElements()[0]; |
| current = statement; |
| } |
| if (blockData.resultVar != null) { |
| PsiDeclarationStatement statement = PsiTreeUtil.getNextSiblingOfType(current, PsiDeclarationStatement.class); |
| resultVar = (PsiLocalVariable)statement.getDeclaredElements()[0]; |
| } |
| } |
| if (statements.length > 0) { |
| final PsiStatement lastStatement = statements[statements.length - 1]; |
| if (lastStatement instanceof PsiReturnStatement && tailCall != InlineUtil.TailCallType.Return) { |
| final PsiExpression returnValue = ((PsiReturnStatement)lastStatement).getReturnValue(); |
| if (returnValue != null && PsiUtil.isStatement(returnValue)) { |
| PsiExpressionStatement exprStatement = (PsiExpressionStatement)myFactory.createStatementFromText("a;", null); |
| exprStatement.getExpression().replace(returnValue); |
| anchorParent.addBefore(exprStatement, anchor); |
| } |
| } |
| } |
| } |
| |
| |
| PsiClass thisClass = myMethod.getContainingClass(); |
| PsiExpression thisAccessExpr; |
| if (thisVar != null) { |
| if (!canInlineParmOrThisVariable(thisVar)) { |
| thisAccessExpr = myFactory.createExpressionFromText(thisVar.getName(), null); |
| } |
| else { |
| thisAccessExpr = thisVar.getInitializer(); |
| } |
| } |
| else { |
| thisAccessExpr = null; |
| } |
| ChangeContextUtil.decodeContextInfo(anchorParent, thisClass, thisAccessExpr); |
| |
| if (methodCall.getParent() instanceof PsiLambdaExpression) { |
| methodCall.delete(); |
| } else if (methodCall.getParent() instanceof PsiExpressionStatement || tailCall == InlineUtil.TailCallType.Return) { |
| methodCall.getParent().delete(); |
| } |
| else { |
| if (blockData.resultVar != null) { |
| PsiExpression expr = myFactory.createExpressionFromText(blockData.resultVar.getName(), null); |
| methodCall.replace(expr); |
| } |
| else { |
| //?? |
| } |
| } |
| |
| if (thisVar != null) { |
| inlineParmOrThisVariable(thisVar, false); |
| } |
| final PsiParameter[] parameters = myMethod.getParameterList().getParameters(); |
| for (int i = 0; i < parmVars.length; i++) { |
| final PsiParameter parameter = parameters[i]; |
| final boolean strictlyFinal = parameter.hasModifierProperty(PsiModifier.FINAL) && isStrictlyFinal(parameter); |
| inlineParmOrThisVariable(parmVars[i], strictlyFinal); |
| } |
| if (resultVar != null) { |
| inlineResultVariable(resultVar); |
| } |
| |
| ChangeContextUtil.clearContextInfo(anchorParent); |
| } |
| |
| private PsiSubstitutor getCallSubstitutor(PsiMethodCallExpression methodCall) { |
| JavaResolveResult resolveResult = methodCall.getMethodExpression().advancedResolve(false); |
| LOG.assertTrue(myManager.areElementsEquivalent(resolveResult.getElement(), myMethod)); |
| if (resolveResult.getSubstitutor() != PsiSubstitutor.EMPTY) { |
| Iterator<PsiTypeParameter> oldTypeParameters = PsiUtil.typeParametersIterator(myMethod); |
| Iterator<PsiTypeParameter> newTypeParameters = PsiUtil.typeParametersIterator(myMethodCopy); |
| PsiSubstitutor substitutor = resolveResult.getSubstitutor(); |
| while (newTypeParameters.hasNext()) { |
| final PsiTypeParameter newTypeParameter = newTypeParameters.next(); |
| final PsiTypeParameter oldTypeParameter = oldTypeParameters.next(); |
| substitutor = substitutor.put(newTypeParameter, resolveResult.getSubstitutor().substitute(oldTypeParameter)); |
| } |
| return substitutor; |
| } |
| |
| return PsiSubstitutor.EMPTY; |
| } |
| |
| private void substituteMethodTypeParams(PsiElement scope, final PsiSubstitutor substitutor) { |
| InlineUtil.substituteTypeParams(scope, substitutor, myFactory); |
| } |
| |
| private boolean isStrictlyFinal(PsiParameter parameter) { |
| for (PsiReference reference : ReferencesSearch.search(parameter, GlobalSearchScope.projectScope(myProject), false)) { |
| final PsiElement refElement = reference.getElement(); |
| final PsiElement anonymousClass = PsiTreeUtil.getParentOfType(refElement, PsiAnonymousClass.class); |
| if (anonymousClass != null && PsiTreeUtil.isAncestor(myMethod, anonymousClass, true)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| private boolean syncNeeded(final PsiReferenceExpression ref) { |
| if (!myMethod.hasModifierProperty(PsiModifier.SYNCHRONIZED)) return false; |
| final PsiMethod containingMethod = Util.getContainingMethod(ref); |
| if (containingMethod == null) return true; |
| if (!containingMethod.hasModifierProperty(PsiModifier.SYNCHRONIZED)) return true; |
| final PsiClass sourceContainingClass = myMethod.getContainingClass(); |
| final PsiClass targetContainingClass = containingMethod.getContainingClass(); |
| return !sourceContainingClass.equals(targetContainingClass); |
| } |
| |
| private BlockData prepareBlock(PsiReferenceExpression ref, |
| final PsiSubstitutor callSubstitutor, |
| final PsiExpressionList argumentList, |
| final InlineUtil.TailCallType tailCallType) |
| throws IncorrectOperationException { |
| final PsiCodeBlock block = myMethodCopy.getBody(); |
| final PsiStatement[] originalStatements = block.getStatements(); |
| |
| PsiLocalVariable resultVar = null; |
| PsiType returnType = callSubstitutor.substitute(myMethod.getReturnType()); |
| String resultName = null; |
| final int applicabilityLevel = PsiUtil.getApplicabilityLevel(myMethod, callSubstitutor, argumentList); |
| if (returnType != null && returnType != PsiType.VOID && tailCallType == InlineUtil.TailCallType.None) { |
| resultName = myJavaCodeStyle.propertyNameToVariableName("result", VariableKind.LOCAL_VARIABLE); |
| resultName = myJavaCodeStyle.suggestUniqueVariableName(resultName, block.getFirstChild(), true); |
| PsiDeclarationStatement declaration = myFactory.createVariableDeclarationStatement(resultName, returnType, null); |
| declaration = (PsiDeclarationStatement)block.addAfter(declaration, null); |
| resultVar = (PsiLocalVariable)declaration.getDeclaredElements()[0]; |
| } |
| |
| PsiParameter[] parms = myMethodCopy.getParameterList().getParameters(); |
| PsiLocalVariable[] parmVars = new PsiLocalVariable[parms.length]; |
| for (int i = parms.length - 1; i >= 0; i--) { |
| PsiParameter parm = parms[i]; |
| String parmName = parm.getName(); |
| String name = parmName; |
| name = myJavaCodeStyle.variableNameToPropertyName(name, VariableKind.PARAMETER); |
| name = myJavaCodeStyle.propertyNameToVariableName(name, VariableKind.LOCAL_VARIABLE); |
| if (!name.equals(parmName)) { |
| name = myJavaCodeStyle.suggestUniqueVariableName(name, block.getFirstChild(), true); |
| } |
| RefactoringUtil.renameVariableReferences(parm, name, new LocalSearchScope(myMethodCopy.getBody()), true); |
| PsiType paramType = parm.getType(); |
| @NonNls String defaultValue; |
| if (paramType instanceof PsiEllipsisType) { |
| final PsiEllipsisType ellipsisType = (PsiEllipsisType)paramType; |
| paramType = callSubstitutor.substitute(ellipsisType.toArrayType()); |
| if (applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS) { |
| defaultValue = "new " + ((PsiArrayType)paramType).getComponentType().getCanonicalText() + "[]{}"; |
| } |
| else { |
| defaultValue = PsiTypesUtil.getDefaultValueOfType(paramType); |
| } |
| } |
| else { |
| defaultValue = PsiTypesUtil.getDefaultValueOfType(paramType); |
| } |
| |
| PsiExpression initializer = myFactory.createExpressionFromText(defaultValue, null); |
| PsiDeclarationStatement declaration = |
| myFactory.createVariableDeclarationStatement(name, callSubstitutor.substitute(paramType), initializer); |
| declaration = (PsiDeclarationStatement)block.addAfter(declaration, null); |
| parmVars[i] = (PsiLocalVariable)declaration.getDeclaredElements()[0]; |
| PsiUtil.setModifierProperty(parmVars[i], PsiModifier.FINAL, parm.hasModifierProperty(PsiModifier.FINAL)); |
| } |
| |
| PsiLocalVariable thisVar = null; |
| PsiClass containingClass = myMethod.getContainingClass(); |
| if (!myMethod.hasModifierProperty(PsiModifier.STATIC) && containingClass != null) { |
| PsiType thisType = myFactory.createType(containingClass, callSubstitutor); |
| String[] names = myJavaCodeStyle.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, thisType) |
| .names; |
| String thisVarName = names[0]; |
| thisVarName = myJavaCodeStyle.suggestUniqueVariableName(thisVarName, myMethod.getFirstChild(), true); |
| PsiExpression initializer = myFactory.createExpressionFromText("null", null); |
| PsiDeclarationStatement declaration = myFactory.createVariableDeclarationStatement(thisVarName, thisType, initializer); |
| declaration = (PsiDeclarationStatement)block.addAfter(declaration, null); |
| thisVar = (PsiLocalVariable)declaration.getDeclaredElements()[0]; |
| } |
| |
| String lockName = null; |
| if (thisVar != null) { |
| lockName = thisVar.getName(); |
| } |
| else if (myMethod.hasModifierProperty(PsiModifier.STATIC) && containingClass != null ) { |
| lockName = containingClass.getQualifiedName() + ".class"; |
| } |
| |
| if (lockName != null && syncNeeded(ref)) { |
| PsiSynchronizedStatement synchronizedStatement = |
| (PsiSynchronizedStatement)myFactory.createStatementFromText("synchronized(" + lockName + "){}", block); |
| synchronizedStatement = (PsiSynchronizedStatement)CodeStyleManager.getInstance(myProject).reformat(synchronizedStatement); |
| synchronizedStatement = (PsiSynchronizedStatement)block.add(synchronizedStatement); |
| final PsiCodeBlock synchronizedBody = synchronizedStatement.getBody(); |
| for (final PsiStatement originalStatement : originalStatements) { |
| synchronizedBody.add(originalStatement); |
| originalStatement.delete(); |
| } |
| } |
| |
| if (resultName != null || tailCallType == InlineUtil.TailCallType.Simple) { |
| PsiReturnStatement[] returnStatements = PsiUtil.findReturnStatements(myMethodCopy); |
| for (PsiReturnStatement returnStatement : returnStatements) { |
| final PsiExpression returnValue = returnStatement.getReturnValue(); |
| if (returnValue == null) continue; |
| PsiStatement statement; |
| if (tailCallType == InlineUtil.TailCallType.Simple) { |
| if (returnValue instanceof PsiExpression && returnStatement.getNextSibling() == myMethodCopy.getBody().getLastBodyElement()) { |
| PsiExpressionStatement exprStatement = (PsiExpressionStatement) myFactory.createStatementFromText("a;", null); |
| exprStatement.getExpression().replace(returnValue); |
| returnStatement.getParent().addBefore(exprStatement, returnStatement); |
| statement = myFactory.createStatementFromText("return;", null); |
| } else { |
| statement = (PsiStatement)returnStatement.copy(); |
| } |
| } |
| else { |
| statement = myFactory.createStatementFromText(resultName + "=0;", null); |
| statement = (PsiStatement)myCodeStyleManager.reformat(statement); |
| PsiAssignmentExpression assignment = (PsiAssignmentExpression)((PsiExpressionStatement)statement).getExpression(); |
| assignment.getRExpression().replace(returnValue); |
| } |
| returnStatement.replace(statement); |
| } |
| } |
| |
| return new BlockData(block, thisVar, parmVars, resultVar); |
| } |
| |
| private void solveVariableNameConflicts(PsiElement scope, final PsiElement placeToInsert) throws IncorrectOperationException { |
| if (scope instanceof PsiVariable) { |
| PsiVariable var = (PsiVariable)scope; |
| String name = var.getName(); |
| String oldName = name; |
| while (true) { |
| String newName = myJavaCodeStyle.suggestUniqueVariableName(name, placeToInsert, true); |
| if (newName.equals(name)) break; |
| name = newName; |
| newName = myJavaCodeStyle.suggestUniqueVariableName(name, var, true); |
| if (newName.equals(name)) break; |
| name = newName; |
| } |
| if (!name.equals(oldName)) { |
| RefactoringUtil.renameVariableReferences(var, name, new LocalSearchScope(myMethodCopy.getBody()), true); |
| var.getNameIdentifier().replace(myFactory.createIdentifier(name)); |
| } |
| } |
| |
| PsiElement[] children = scope.getChildren(); |
| for (PsiElement child : children) { |
| solveVariableNameConflicts(child, placeToInsert); |
| } |
| } |
| |
| private void addParmAndThisVarInitializers(BlockData blockData, PsiMethodCallExpression methodCall) throws IncorrectOperationException { |
| PsiExpression[] args = methodCall.getArgumentList().getExpressions(); |
| if (blockData.parmVars.length > 0) { |
| for (int i = 0; i < args.length; i++) { |
| int j = Math.min(i, blockData.parmVars.length - 1); |
| final PsiExpression initializer = blockData.parmVars[j].getInitializer(); |
| LOG.assertTrue(initializer != null); |
| if (initializer instanceof PsiNewExpression && ((PsiNewExpression)initializer).getArrayInitializer() != null) { //varargs initializer |
| final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)initializer).getArrayInitializer(); |
| arrayInitializer.add(args[i]); |
| continue; |
| } |
| |
| initializer.replace(args[i]); |
| } |
| } |
| |
| if (blockData.thisVar != null) { |
| PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); |
| if (qualifier == null) { |
| PsiElement parent = methodCall.getContext(); |
| while (true) { |
| if (parent instanceof PsiClass) break; |
| if (parent instanceof PsiFile) break; |
| assert parent != null : methodCall; |
| parent = parent.getContext(); |
| } |
| if (parent instanceof PsiClass) { |
| PsiClass parentClass = (PsiClass)parent; |
| final PsiClass containingClass = myMethod.getContainingClass(); |
| if (InheritanceUtil.isInheritorOrSelf(parentClass, containingClass, true)) { |
| qualifier = myFactory.createExpressionFromText("this", null); |
| } |
| else { |
| if (PsiTreeUtil.isAncestor(containingClass, parent, false)) { |
| String name = containingClass.getName(); |
| if (name != null) { |
| qualifier = myFactory.createExpressionFromText(name + ".this", null); |
| } |
| else { //? |
| qualifier = myFactory.createExpressionFromText("this", null); |
| } |
| } else { // we are inside the inheritor |
| do { |
| parentClass = PsiTreeUtil.getParentOfType(parentClass, PsiClass.class, true); |
| if (InheritanceUtil.isInheritorOrSelf(parentClass, containingClass, true)) { |
| LOG.assertTrue(parentClass != null); |
| final String childClassName = parentClass.getName(); |
| qualifier = myFactory.createExpressionFromText(childClassName != null ? childClassName + ".this" : "this", null); |
| break; |
| } |
| } |
| while (parentClass != null); |
| } |
| } |
| } |
| else { |
| qualifier = myFactory.createExpressionFromText("this", null); |
| } |
| } |
| else if (qualifier instanceof PsiSuperExpression) { |
| qualifier = myFactory.createExpressionFromText("this", null); |
| } |
| blockData.thisVar.getInitializer().replace(qualifier); |
| } |
| } |
| |
| private boolean canInlineParmOrThisVariable(PsiLocalVariable variable) { |
| boolean isAccessedForWriting = false; |
| for (PsiReference ref : ReferencesSearch.search(variable)) { |
| PsiElement refElement = ref.getElement(); |
| if (refElement instanceof PsiExpression) { |
| if (PsiUtil.isAccessedForWriting((PsiExpression)refElement)) { |
| isAccessedForWriting = true; |
| } |
| } |
| } |
| |
| PsiExpression initializer = variable.getInitializer(); |
| boolean shouldBeFinal = variable.hasModifierProperty(PsiModifier.FINAL) && false; |
| return canInlineParmOrThisVariable(initializer, shouldBeFinal, false, ReferencesSearch.search(variable).findAll().size(), isAccessedForWriting); |
| } |
| |
| private void inlineParmOrThisVariable(PsiLocalVariable variable, boolean strictlyFinal) throws IncorrectOperationException { |
| PsiReference firstRef = ReferencesSearch.search(variable).findFirst(); |
| |
| if (firstRef == null) { |
| variable.getParent().delete(); //Q: side effects? |
| return; |
| } |
| |
| |
| boolean isAccessedForWriting = false; |
| final Collection<PsiReference> refs = ReferencesSearch.search(variable).findAll(); |
| for (PsiReference ref : refs) { |
| PsiElement refElement = ref.getElement(); |
| if (refElement instanceof PsiExpression) { |
| if (PsiUtil.isAccessedForWriting((PsiExpression)refElement)) { |
| isAccessedForWriting = true; |
| } |
| } |
| } |
| |
| PsiExpression initializer = variable.getInitializer(); |
| boolean shouldBeFinal = variable.hasModifierProperty(PsiModifier.FINAL) && strictlyFinal; |
| if (canInlineParmOrThisVariable(initializer, shouldBeFinal, strictlyFinal, refs.size(), isAccessedForWriting)) { |
| if (shouldBeFinal) { |
| declareUsedLocalsFinal(initializer, strictlyFinal); |
| } |
| for (PsiReference ref : refs) { |
| final PsiJavaCodeReferenceElement javaRef = (PsiJavaCodeReferenceElement)ref; |
| if (initializer instanceof PsiThisExpression && ((PsiThisExpression)initializer).getQualifier() == null) { |
| final PsiClass varThisClass = RefactoringChangeUtil.getThisClass(variable); |
| if (RefactoringChangeUtil.getThisClass(javaRef) != varThisClass) { |
| initializer = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createExpressionFromText(varThisClass.getName() + ".this", variable); |
| } |
| } |
| |
| PsiExpression expr = InlineUtil.inlineVariable(variable, initializer, javaRef); |
| |
| InlineUtil.tryToInlineArrayCreationForVarargs(expr); |
| |
| //Q: move the following code to some util? (addition to inline?) |
| if (expr instanceof PsiThisExpression) { |
| if (expr.getParent() instanceof PsiReferenceExpression) { |
| PsiReferenceExpression refExpr = (PsiReferenceExpression)expr.getParent(); |
| PsiElement refElement = refExpr.resolve(); |
| PsiExpression exprCopy = (PsiExpression)refExpr.copy(); |
| refExpr = (PsiReferenceExpression)refExpr.replace(myFactory.createExpressionFromText(refExpr.getReferenceName(), null)); |
| if (refElement != null) { |
| PsiElement newRefElement = refExpr.resolve(); |
| if (!refElement.equals(newRefElement)) { |
| // change back |
| refExpr.replace(exprCopy); |
| } |
| } |
| } |
| } |
| } |
| variable.getParent().delete(); |
| } |
| } |
| |
| private boolean canInlineParmOrThisVariable(PsiExpression initializer, |
| boolean shouldBeFinal, |
| boolean strictlyFinal, |
| int accessCount, |
| boolean isAccessedForWriting) { |
| if (strictlyFinal) { |
| class CanAllLocalsBeDeclaredFinal extends JavaRecursiveElementWalkingVisitor { |
| boolean success = true; |
| |
| @Override public void visitReferenceExpression(PsiReferenceExpression expression) { |
| final PsiElement psiElement = expression.resolve(); |
| if (psiElement instanceof PsiLocalVariable || psiElement instanceof PsiParameter) { |
| if (!RefactoringUtil.canBeDeclaredFinal((PsiVariable)psiElement)) { |
| success = false; |
| } |
| } |
| } |
| |
| @Override public void visitElement(PsiElement element) { |
| if (success) { |
| super.visitElement(element); |
| } |
| } |
| } |
| |
| final CanAllLocalsBeDeclaredFinal canAllLocalsBeDeclaredFinal = new CanAllLocalsBeDeclaredFinal(); |
| initializer.accept(canAllLocalsBeDeclaredFinal); |
| if (!canAllLocalsBeDeclaredFinal.success) return false; |
| } |
| if (initializer instanceof PsiMethodReferenceExpression) return true; |
| if (initializer instanceof PsiReferenceExpression) { |
| PsiVariable refVar = (PsiVariable)((PsiReferenceExpression)initializer).resolve(); |
| if (refVar == null) { |
| return !isAccessedForWriting; |
| } |
| if (refVar instanceof PsiField) { |
| if (isAccessedForWriting) return false; |
| /* |
| PsiField field = (PsiField)refVar; |
| if (isFieldNonModifiable(field)){ |
| return true; |
| } |
| //TODO: other cases |
| return false; |
| */ |
| return true; //TODO: "suspicous" places to review by user! |
| } |
| else { |
| if (isAccessedForWriting) { |
| if (refVar.hasModifierProperty(PsiModifier.FINAL) || shouldBeFinal) return false; |
| PsiReference[] refs = |
| ReferencesSearch.search(refVar, GlobalSearchScope.projectScope(myProject), false).toArray(new PsiReference[0]); |
| return refs.length == 1; //TODO: control flow |
| } |
| else { |
| if (shouldBeFinal) { |
| return refVar.hasModifierProperty(PsiModifier.FINAL) || RefactoringUtil.canBeDeclaredFinal(refVar); |
| } |
| return true; |
| } |
| } |
| } |
| else if (isAccessedForWriting) { |
| return false; |
| } |
| else if (initializer instanceof PsiCallExpression) { |
| if (accessCount > 1) return false; |
| if (initializer instanceof PsiNewExpression) { |
| final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)initializer).getArrayInitializer(); |
| if (arrayInitializer != null) { |
| for (PsiExpression expression : arrayInitializer.getInitializers()) { |
| if (!canInlineParmOrThisVariable(expression, shouldBeFinal, strictlyFinal, accessCount, false)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| final PsiExpressionList argumentList = ((PsiCallExpression)initializer).getArgumentList(); |
| if (argumentList == null) return false; |
| final PsiExpression[] expressions = argumentList.getExpressions(); |
| for (PsiExpression expression : expressions) { |
| if (!canInlineParmOrThisVariable(expression, shouldBeFinal, strictlyFinal, accessCount, false)) { |
| return false; |
| } |
| } |
| return true; //TODO: "suspicous" places to review by user! |
| } |
| else if (initializer instanceof PsiLiteralExpression) { |
| return true; |
| } |
| else if (initializer instanceof PsiArrayAccessExpression) { |
| final PsiExpression arrayExpression = ((PsiArrayAccessExpression)initializer).getArrayExpression(); |
| final PsiExpression indexExpression = ((PsiArrayAccessExpression)initializer).getIndexExpression(); |
| return canInlineParmOrThisVariable(arrayExpression, shouldBeFinal, strictlyFinal, accessCount, false) && |
| canInlineParmOrThisVariable(indexExpression, shouldBeFinal, strictlyFinal, accessCount, false); |
| } |
| else if (initializer instanceof PsiParenthesizedExpression) { |
| PsiExpression expr = ((PsiParenthesizedExpression)initializer).getExpression(); |
| return expr == null || canInlineParmOrThisVariable(expr, shouldBeFinal, strictlyFinal, accessCount, false); |
| } |
| else if (initializer instanceof PsiTypeCastExpression) { |
| PsiExpression operand = ((PsiTypeCastExpression)initializer).getOperand(); |
| return operand != null && canInlineParmOrThisVariable(operand, shouldBeFinal, strictlyFinal, accessCount, false); |
| } |
| else if (initializer instanceof PsiPolyadicExpression) { |
| PsiPolyadicExpression binExpr = (PsiPolyadicExpression)initializer; |
| for (PsiExpression op : binExpr.getOperands()) { |
| if (!canInlineParmOrThisVariable(op, shouldBeFinal, strictlyFinal, accessCount, false)) return false; |
| } |
| return true; |
| } |
| else if (initializer instanceof PsiClassObjectAccessExpression) { |
| return true; |
| } |
| else if (initializer instanceof PsiThisExpression) { |
| return true; |
| } |
| else if (initializer instanceof PsiSuperExpression) { |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| |
| private static void declareUsedLocalsFinal(PsiElement expr, boolean strictlyFinal) throws IncorrectOperationException { |
| if (expr instanceof PsiReferenceExpression) { |
| PsiElement refElement = ((PsiReferenceExpression)expr).resolve(); |
| if (refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) { |
| if (strictlyFinal || RefactoringUtil.canBeDeclaredFinal((PsiVariable)refElement)) { |
| PsiUtil.setModifierProperty(((PsiVariable)refElement), PsiModifier.FINAL, true); |
| } |
| } |
| } |
| PsiElement[] children = expr.getChildren(); |
| for (PsiElement child : children) { |
| declareUsedLocalsFinal(child, strictlyFinal); |
| } |
| } |
| |
| /* |
| private boolean isFieldNonModifiable(PsiField field) { |
| if (field.hasModifierProperty(PsiModifier.FINAL)){ |
| return true; |
| } |
| PsiElement[] refs = myManager.getSearchHelper().findReferences(field, null, false); |
| for(int i = 0; i < refs.length; i++){ |
| PsiReferenceExpression ref = (PsiReferenceExpression)refs[i]; |
| if (PsiUtil.isAccessedForWriting(ref)) { |
| PsiElement container = ref.getParent(); |
| while(true){ |
| if (container instanceof PsiMethod || |
| container instanceof PsiField || |
| container instanceof PsiClassInitializer || |
| container instanceof PsiFile) break; |
| container = container.getParent(); |
| } |
| if (container instanceof PsiMethod && ((PsiMethod)container).isConstructor()) continue; |
| return false; |
| } |
| } |
| return true; |
| } |
| */ |
| |
| private void inlineResultVariable(PsiVariable resultVar) throws IncorrectOperationException { |
| PsiAssignmentExpression assignment = null; |
| PsiReferenceExpression resultUsage = null; |
| for (PsiReference ref1 : ReferencesSearch.search(resultVar, GlobalSearchScope.projectScope(myProject), false)) { |
| PsiReferenceExpression ref = (PsiReferenceExpression)ref1; |
| if (ref.getParent() instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)ref.getParent()).getLExpression().equals(ref)) { |
| if (assignment != null) { |
| assignment = null; |
| break; |
| } |
| else { |
| assignment = (PsiAssignmentExpression)ref.getParent(); |
| } |
| } |
| else { |
| LOG.assertTrue(resultUsage == null, "old:" + resultUsage + "; new:" + ref); |
| resultUsage = ref; |
| } |
| } |
| |
| if (assignment == null) return; |
| boolean condition = assignment.getParent() instanceof PsiExpressionStatement; |
| LOG.assertTrue(condition); |
| // SCR3175 fixed: inline only if declaration and assignment is in the same code block. |
| if (!(assignment.getParent().getParent() == resultVar.getParent().getParent())) return; |
| if (resultUsage != null) { |
| String name = resultVar.getName(); |
| PsiDeclarationStatement declaration = |
| myFactory.createVariableDeclarationStatement(name, resultVar.getType(), assignment.getRExpression()); |
| declaration = (PsiDeclarationStatement)assignment.getParent().replace(declaration); |
| resultVar.getParent().delete(); |
| resultVar = (PsiVariable)declaration.getDeclaredElements()[0]; |
| |
| PsiElement parentStatement = RefactoringUtil.getParentStatement(resultUsage, true); |
| PsiElement next = declaration.getNextSibling(); |
| boolean canInline = false; |
| while (true) { |
| if (next == null) break; |
| if (parentStatement.equals(next)) { |
| canInline = true; |
| break; |
| } |
| if (next instanceof PsiStatement) break; |
| next = next.getNextSibling(); |
| } |
| |
| if (canInline) { |
| InlineUtil.inlineVariable(resultVar, resultVar.getInitializer(), resultUsage); |
| declaration.delete(); |
| } |
| } |
| else { |
| PsiExpression rExpression = assignment.getRExpression(); |
| while (rExpression instanceof PsiReferenceExpression) rExpression = ((PsiReferenceExpression)rExpression).getQualifierExpression(); |
| if (rExpression == null || !PsiUtil.isStatement(rExpression)) { |
| assignment.delete(); |
| } |
| else { |
| assignment.replace(rExpression); |
| } |
| resultVar.delete(); |
| } |
| } |
| |
| private static final Key<String> MARK_KEY = Key.create(""); |
| |
| private PsiReferenceExpression[] addBracesWhenNeeded(PsiReferenceExpression[] refs) throws IncorrectOperationException { |
| ArrayList<PsiReferenceExpression> refsVector = new ArrayList<PsiReferenceExpression>(); |
| ArrayList<PsiCodeBlock> addedBracesVector = new ArrayList<PsiCodeBlock>(); |
| myAddedClassInitializers = new HashMap<PsiField, PsiClassInitializer>(); |
| |
| for (PsiReferenceExpression ref : refs) { |
| ref.putCopyableUserData(MARK_KEY, ""); |
| } |
| |
| RefLoop: |
| for (PsiReferenceExpression ref : refs) { |
| if (!ref.isValid()) continue; |
| |
| PsiElement parentStatement = RefactoringUtil.getParentStatement(ref, true); |
| if (parentStatement != null) { |
| PsiElement parent = ref.getParent(); |
| while (!parent.equals(parentStatement)) { |
| if (parent instanceof PsiExpressionStatement) { |
| String text = "{\n}"; |
| PsiBlockStatement blockStatement = (PsiBlockStatement)myFactory.createStatementFromText(text, null); |
| blockStatement = (PsiBlockStatement)myCodeStyleManager.reformat(blockStatement); |
| blockStatement.getCodeBlock().add(parent); |
| blockStatement = (PsiBlockStatement)parent.replace(blockStatement); |
| |
| PsiElement newStatement = blockStatement.getCodeBlock().getStatements()[0]; |
| addMarkedElements(refsVector, newStatement); |
| addedBracesVector.add(blockStatement.getCodeBlock()); |
| continue RefLoop; |
| } |
| parent = parent.getParent(); |
| } |
| final PsiElement lambdaExpr = parentStatement.getParent(); |
| if (lambdaExpr instanceof PsiLambdaExpression) { |
| final PsiLambdaExpression newLambdaExpr = (PsiLambdaExpression)myFactory.createExpressionFromText( |
| ((PsiLambdaExpression)lambdaExpr).getParameterList().getText() + " -> " + "{\n}", lambdaExpr); |
| final PsiStatement statementFromText; |
| if (LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)lambdaExpr) == PsiType.VOID ) { |
| statementFromText = myFactory.createStatementFromText("a;", lambdaExpr); |
| ((PsiExpressionStatement)statementFromText).getExpression().replace(parentStatement); |
| } else { |
| statementFromText = myFactory.createStatementFromText("return a;", lambdaExpr); |
| ((PsiReturnStatement)statementFromText).getReturnValue().replace(parentStatement); |
| } |
| |
| newLambdaExpr.getBody().add(statementFromText); |
| |
| final PsiCodeBlock body = (PsiCodeBlock)((PsiLambdaExpression)lambdaExpr.replace(newLambdaExpr)).getBody(); |
| PsiElement newStatement = body.getStatements()[0]; |
| addMarkedElements(refsVector, newStatement); |
| addedBracesVector.add(body); |
| continue; |
| |
| } |
| } |
| else { |
| final PsiField field = PsiTreeUtil.getParentOfType(ref, PsiField.class); |
| if (field != null) { |
| if (field instanceof PsiEnumConstant) { |
| inlineEnumConstantParameter(refsVector, ref); |
| continue; |
| } |
| field.normalizeDeclaration(); |
| final PsiExpression initializer = field.getInitializer(); |
| LOG.assertTrue(initializer != null); |
| PsiClassInitializer classInitializer = myFactory.createClassInitializer(); |
| final PsiClass containingClass = field.getContainingClass(); |
| classInitializer = (PsiClassInitializer)containingClass.addAfter(classInitializer, field); |
| containingClass.addAfter(CodeEditUtil.createLineFeed(field.getManager()), field); |
| final PsiCodeBlock body = classInitializer.getBody(); |
| PsiExpressionStatement statement = (PsiExpressionStatement)myFactory.createStatementFromText(field.getName() + " = 0;", body); |
| statement = (PsiExpressionStatement)body.add(statement); |
| final PsiAssignmentExpression assignment = (PsiAssignmentExpression)statement.getExpression(); |
| assignment.getLExpression().replace(RenameJavaVariableProcessor.createMemberReference(field, assignment)); |
| assignment.getRExpression().replace(initializer); |
| addMarkedElements(refsVector, statement); |
| if (field.hasModifierProperty(PsiModifier.STATIC)) { |
| PsiUtil.setModifierProperty(classInitializer, PsiModifier.STATIC, true); |
| } |
| myAddedClassInitializers.put(field, classInitializer); |
| continue; |
| } |
| } |
| |
| refsVector.add(ref); |
| } |
| |
| for (PsiReferenceExpression ref : refs) { |
| ref.putCopyableUserData(MARK_KEY, null); |
| } |
| |
| myAddedBraces = addedBracesVector.toArray(new PsiCodeBlock[addedBracesVector.size()]); |
| return refsVector.toArray(new PsiReferenceExpression[refsVector.size()]); |
| } |
| |
| private void inlineEnumConstantParameter(final List<PsiReferenceExpression> refsVector, |
| final PsiReferenceExpression ref) throws IncorrectOperationException { |
| PsiExpression expr = getSimpleReturnedExpression(myMethod); |
| if (expr != null) { |
| refsVector.add(ref); |
| } |
| else { |
| PsiCall call = PsiTreeUtil.getParentOfType(ref, PsiCall.class); |
| @NonNls String text = "new Object() { " + myMethod.getReturnTypeElement().getText() + " evaluate() { return " + call.getText() + ";}}.evaluate"; |
| PsiExpression callExpr = JavaPsiFacade.getInstance(myProject).getParserFacade().createExpressionFromText(text, call); |
| PsiElement classExpr = ref.replace(callExpr); |
| classExpr.accept(new JavaRecursiveElementWalkingVisitor() { |
| public void visitReturnStatement(final PsiReturnStatement statement) { |
| super.visitReturnStatement(statement); |
| PsiExpression expr = statement.getReturnValue(); |
| if (expr instanceof PsiMethodCallExpression) { |
| refsVector.add(((PsiMethodCallExpression) expr).getMethodExpression()); |
| } |
| } |
| }); |
| if (classExpr.getParent() instanceof PsiMethodCallExpression) { |
| PsiExpressionList args = ((PsiMethodCallExpression)classExpr.getParent()).getArgumentList(); |
| PsiExpression[] argExpressions = args.getExpressions(); |
| if (argExpressions.length > 0) { |
| args.deleteChildRange(argExpressions [0], argExpressions [argExpressions.length-1]); |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private static PsiExpression getSimpleReturnedExpression(final PsiMethod method) { |
| PsiCodeBlock body = method.getBody(); |
| if (body == null) return null; |
| PsiStatement[] psiStatements = body.getStatements(); |
| if (psiStatements.length != 1) return null; |
| PsiStatement statement = psiStatements[0]; |
| if (!(statement instanceof PsiReturnStatement)) return null; |
| return ((PsiReturnStatement) statement).getReturnValue(); |
| } |
| |
| private static void addMarkedElements(final List<PsiReferenceExpression> array, PsiElement scope) { |
| scope.accept(new PsiRecursiveElementWalkingVisitor() { |
| @Override public void visitElement(PsiElement element) { |
| if (element.getCopyableUserData(MARK_KEY) != null) { |
| array.add((PsiReferenceExpression)element); |
| element.putCopyableUserData(MARK_KEY, null); |
| } |
| super.visitElement(element); |
| } |
| }); |
| } |
| |
| private void removeAddedBracesWhenPossible() throws IncorrectOperationException { |
| if (myAddedBraces == null) return; |
| |
| for (PsiCodeBlock codeBlock : myAddedBraces) { |
| PsiStatement[] statements = codeBlock.getStatements(); |
| if (statements.length == 1) { |
| final PsiElement codeBlockParent = codeBlock.getParent(); |
| if (codeBlockParent instanceof PsiLambdaExpression) { |
| if (statements[0] instanceof PsiReturnStatement) { |
| final PsiExpression returnValue = ((PsiReturnStatement)statements[0]).getReturnValue(); |
| if (returnValue != null) { |
| codeBlock.replace(returnValue); |
| } |
| } else if (statements[0] instanceof PsiExpressionStatement){ |
| codeBlock.replace(((PsiExpressionStatement)statements[0]).getExpression()); |
| } |
| } |
| else { |
| if (codeBlockParent instanceof PsiBlockStatement) { |
| codeBlockParent.replace(statements[0]); |
| } else { |
| codeBlock.replace(statements[0]); |
| } |
| } |
| } |
| } |
| |
| final Set<PsiField> fields = myAddedClassInitializers.keySet(); |
| |
| for (PsiField psiField : fields) { |
| final PsiClassInitializer classInitializer = myAddedClassInitializers.get(psiField); |
| final PsiExpression initializer = getSimpleFieldInitializer(psiField, classInitializer); |
| if (initializer != null) { |
| psiField.getInitializer().replace(initializer); |
| classInitializer.delete(); |
| } |
| else { |
| psiField.getInitializer().delete(); |
| } |
| } |
| } |
| |
| @Nullable |
| private PsiExpression getSimpleFieldInitializer(PsiField field, PsiClassInitializer initializer) { |
| final PsiStatement[] statements = initializer.getBody().getStatements(); |
| if (statements.length != 1) return null; |
| if (!(statements[0] instanceof PsiExpressionStatement)) return null; |
| final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); |
| if (!(expression instanceof PsiAssignmentExpression)) return null; |
| final PsiExpression lExpression = ((PsiAssignmentExpression)expression).getLExpression(); |
| if (!(lExpression instanceof PsiReferenceExpression)) return null; |
| final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve(); |
| if (!myManager.areElementsEquivalent(field, resolved)) return null; |
| return ((PsiAssignmentExpression)expression).getRExpression(); |
| } |
| |
| public static String checkCalledInSuperOrThisExpr(PsiCodeBlock methodBody, final PsiElement element) { |
| if (methodBody.getStatements().length > 1) { |
| PsiExpression expr = PsiTreeUtil.getParentOfType(element, PsiExpression.class); |
| while (expr != null) { |
| if (RefactoringChangeUtil.isSuperOrThisMethodCall(expr)) { |
| return "Inline cannot be applied to multiline method in constructor call"; |
| } |
| expr = PsiTreeUtil.getParentOfType(expr, PsiExpression.class, true); |
| } |
| } |
| return null; |
| } |
| |
| public static boolean checkBadReturns(PsiMethod method) { |
| PsiReturnStatement[] returns = PsiUtil.findReturnStatements(method); |
| if (returns.length == 0) return false; |
| PsiCodeBlock body = method.getBody(); |
| ControlFlow controlFlow; |
| try { |
| controlFlow = ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, new LocalsControlFlowPolicy(body), false); |
| } |
| catch (AnalysisCanceledException e) { |
| return false; |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Control flow:"); |
| LOG.debug(controlFlow.toString()); |
| } |
| |
| List<Instruction> instructions = new ArrayList<Instruction>(controlFlow.getInstructions()); |
| |
| // temporary replace all return's with empty statements in the flow |
| for (PsiReturnStatement aReturn : returns) { |
| int offset = controlFlow.getStartOffset(aReturn); |
| int endOffset = controlFlow.getEndOffset(aReturn); |
| while (offset <= endOffset && !(instructions.get(offset) instanceof GoToInstruction)) { |
| offset++; |
| } |
| LOG.assertTrue(instructions.get(offset) instanceof GoToInstruction); |
| instructions.set(offset, EmptyInstruction.INSTANCE); |
| } |
| |
| for (PsiReturnStatement aReturn : returns) { |
| int offset = controlFlow.getEndOffset(aReturn); |
| while (true) { |
| if (offset == instructions.size()) break; |
| Instruction instruction = instructions.get(offset); |
| if (instruction instanceof GoToInstruction) { |
| offset = ((GoToInstruction)instruction).offset; |
| } |
| else if (instruction instanceof ThrowToInstruction) { |
| offset = ((ThrowToInstruction)instruction).offset; |
| } |
| else if (instruction instanceof ConditionalThrowToInstruction) { |
| // In case of "conditional throw to", control flow will not be altered |
| // If exception handler is in method, we will inline it to ivokation site |
| // If exception handler is at invocation site, execution will continue to get there |
| offset++; |
| } |
| else { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private static class BlockData { |
| final PsiCodeBlock block; |
| final PsiLocalVariable thisVar; |
| final PsiLocalVariable[] parmVars; |
| final PsiLocalVariable resultVar; |
| |
| public BlockData(PsiCodeBlock block, PsiLocalVariable thisVar, PsiLocalVariable[] parmVars, PsiLocalVariable resultVar) { |
| this.block = block; |
| this.thisVar = thisVar; |
| this.parmVars = parmVars; |
| this.resultVar = resultVar; |
| } |
| } |
| |
| @NotNull |
| protected Collection<? extends PsiElement> getElementsToWrite(@NotNull final UsageViewDescriptor descriptor) { |
| if (myInlineThisOnly) { |
| return Collections.singletonList(myReference); |
| } |
| else { |
| if (!checkReadOnly()) return Collections.emptyList(); |
| return myReference == null ? Collections.singletonList(myMethod) : Arrays.asList(myReference, myMethod); |
| } |
| } |
| } |