| /* |
| * Copyright 2000-2012 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.extractMethod; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.codeInsight.ExceptionUtil; |
| import com.intellij.codeInsight.NullableNotNullManager; |
| import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; |
| import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil; |
| import com.intellij.codeInsight.highlighting.HighlightManager; |
| import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix; |
| import com.intellij.codeInsight.navigation.NavigationUtil; |
| import com.intellij.codeInspection.dataFlow.RunnerResult; |
| import com.intellij.codeInspection.dataFlow.StandardDataFlowRunner; |
| import com.intellij.codeInspection.dataFlow.StandardInstructionVisitor; |
| import com.intellij.codeInspection.dataFlow.instructions.BranchingInstruction; |
| import com.intellij.codeInspection.dataFlow.instructions.Instruction; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.util.PsiClassListCellRenderer; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.editor.ScrollType; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pass; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.WindowManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.controlFlow.ControlFlowUtil; |
| import com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.PsiElementProcessor; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.*; |
| import com.intellij.refactoring.HelpID; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.extractMethodObject.ExtractMethodObjectHandler; |
| import com.intellij.refactoring.introduceField.ElementToWorkOn; |
| import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; |
| import com.intellij.refactoring.util.*; |
| import com.intellij.refactoring.util.classMembers.ElementNeedsThis; |
| import com.intellij.refactoring.util.duplicates.*; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import com.intellij.util.VisibilityUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.util.*; |
| |
| public class ExtractMethodProcessor implements MatchProvider { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.extractMethod.ExtractMethodProcessor"); |
| |
| protected final Project myProject; |
| private final Editor myEditor; |
| protected final PsiElement[] myElements; |
| private final PsiBlockStatement myEnclosingBlockStatement; |
| private final PsiType myForcedReturnType; |
| private final String myRefactoringName; |
| protected final String myInitialMethodName; |
| private final String myHelpId; |
| |
| private final PsiManager myManager; |
| private final PsiElementFactory myElementFactory; |
| private final CodeStyleManager myStyleManager; |
| |
| private PsiExpression myExpression; |
| |
| private PsiElement myCodeFragmentMember; // parent of myCodeFragment |
| |
| protected String myMethodName; // name for extracted method |
| protected PsiType myReturnType; // return type for extracted method |
| protected PsiTypeParameterList myTypeParameterList; //type parameter list of extracted method |
| private VariableData[] myVariableDatum; // parameter data for extracted method |
| protected PsiClassType[] myThrownExceptions; // exception to declare as thrown by extracted method |
| protected boolean myStatic; // whether to declare extracted method static |
| |
| protected PsiClass myTargetClass; // class to create the extracted method in |
| private PsiElement myAnchor; // anchor to insert extracted method after it |
| |
| protected ControlFlowWrapper myControlFlowWrapper; |
| protected InputVariables myInputVariables; // input variables |
| protected PsiVariable[] myOutputVariables; // output variables |
| protected PsiVariable myOutputVariable; // the only output variable |
| private Collection<PsiStatement> myExitStatements; |
| |
| private boolean myHasReturnStatement; // there is a return statement |
| private boolean myHasReturnStatementOutput; // there is a return statement and its type is not void |
| protected boolean myHasExpressionOutput; // extracted code is an expression with non-void type |
| private boolean myNeedChangeContext; // target class is not immediate container of the code to be extracted |
| |
| private boolean myShowErrorDialogs = true; |
| protected boolean myCanBeStatic; |
| protected boolean myCanBeChainedConstructor; |
| protected boolean myIsChainedConstructor; |
| private DuplicatesFinder myDuplicatesFinder; |
| private List<Match> myDuplicates; |
| @PsiModifier.ModifierConstant private String myMethodVisibility = PsiModifier.PRIVATE; |
| protected boolean myGenerateConditionalExit; |
| protected PsiStatement myFirstExitStatementCopy; |
| private PsiMethod myExtractedMethod; |
| private PsiMethodCallExpression myMethodCall; |
| private boolean myNullConditionalCheck = false; |
| |
| public ExtractMethodProcessor(Project project, |
| Editor editor, |
| PsiElement[] elements, |
| PsiType forcedReturnType, |
| String refactoringName, |
| String initialMethodName, |
| String helpId) { |
| myProject = project; |
| myEditor = editor; |
| if (elements.length != 1 || elements.length == 1 && !(elements[0] instanceof PsiBlockStatement)) { |
| myElements = elements; |
| myEnclosingBlockStatement = null; |
| } |
| else { |
| myEnclosingBlockStatement = (PsiBlockStatement)elements[0]; |
| PsiElement[] codeBlockChildren = myEnclosingBlockStatement.getCodeBlock().getChildren(); |
| myElements = processCodeBlockChildren(codeBlockChildren); |
| } |
| myForcedReturnType = forcedReturnType; |
| myRefactoringName = refactoringName; |
| myInitialMethodName = initialMethodName; |
| myHelpId = helpId; |
| |
| myManager = PsiManager.getInstance(myProject); |
| myElementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); |
| myStyleManager = CodeStyleManager.getInstance(myProject); |
| } |
| |
| private static PsiElement[] processCodeBlockChildren(PsiElement[] codeBlockChildren) { |
| int resultLast = codeBlockChildren.length; |
| |
| if (codeBlockChildren.length == 0) return PsiElement.EMPTY_ARRAY; |
| |
| final PsiElement first = codeBlockChildren[0]; |
| int resultStart = 0; |
| if (first instanceof PsiJavaToken && ((PsiJavaToken)first).getTokenType() == JavaTokenType.LBRACE) { |
| resultStart++; |
| } |
| final PsiElement last = codeBlockChildren[codeBlockChildren.length - 1]; |
| if (last instanceof PsiJavaToken && ((PsiJavaToken)last).getTokenType() == JavaTokenType.RBRACE) { |
| resultLast--; |
| } |
| final ArrayList<PsiElement> result = new ArrayList<PsiElement>(); |
| for (int i = resultStart; i < resultLast; i++) { |
| PsiElement element = codeBlockChildren[i]; |
| if (!(element instanceof PsiWhiteSpace)) { |
| result.add(element); |
| } |
| } |
| |
| return PsiUtilCore.toPsiElementArray(result); |
| } |
| |
| /** |
| * Method for test purposes |
| */ |
| public void setShowErrorDialogs(boolean showErrorDialogs) { |
| myShowErrorDialogs = showErrorDialogs; |
| } |
| |
| public void setChainedConstructor(final boolean isChainedConstructor) { |
| myIsChainedConstructor = isChainedConstructor; |
| } |
| |
| |
| public boolean prepare() throws PrepareFailedException { |
| return prepare(null); |
| } |
| |
| /** |
| * Invoked in atomic action |
| */ |
| public boolean prepare(@Nullable Pass<ExtractMethodProcessor> pass) throws PrepareFailedException { |
| myExpression = null; |
| if (myElements.length == 1 && myElements[0] instanceof PsiExpression) { |
| final PsiExpression expression = (PsiExpression)myElements[0]; |
| if (expression instanceof PsiAssignmentExpression && expression.getParent() instanceof PsiExpressionStatement) { |
| myElements[0] = expression.getParent(); |
| } |
| else { |
| myExpression = expression; |
| } |
| } |
| |
| final PsiElement codeFragment = ControlFlowUtil.findCodeFragment(myElements[0]); |
| myCodeFragmentMember = codeFragment.getUserData(ElementToWorkOn.PARENT); |
| if (myCodeFragmentMember == null) { |
| myCodeFragmentMember = codeFragment.getParent(); |
| } |
| if (myCodeFragmentMember == null) { |
| myCodeFragmentMember = ControlFlowUtil.findCodeFragment(codeFragment.getContext()).getParent(); |
| } |
| |
| myControlFlowWrapper = new ControlFlowWrapper(myProject, codeFragment, myElements); |
| |
| try { |
| myExitStatements = myControlFlowWrapper.prepareExitStatements(myElements); |
| if (myControlFlowWrapper.isGenerateConditionalExit()) { |
| myGenerateConditionalExit = true; |
| } else { |
| myHasReturnStatement = myExpression == null && myControlFlowWrapper.isReturnPresentBetween(); |
| } |
| myFirstExitStatementCopy = myControlFlowWrapper.getFirstExitStatementCopy(); |
| } |
| catch (ControlFlowWrapper.ExitStatementsNotSameException e) { |
| myExitStatements = myControlFlowWrapper.getExitStatements(); |
| showMultipleExitPointsMessage(); |
| return false; |
| } |
| |
| myOutputVariables = myControlFlowWrapper.getOutputVariables(); |
| |
| return chooseTargetClass(codeFragment, pass); |
| } |
| |
| private boolean checkExitPoints() throws PrepareFailedException { |
| PsiType expressionType = null; |
| if (myExpression != null) { |
| if (myForcedReturnType != null) { |
| expressionType = myForcedReturnType; |
| } |
| else { |
| expressionType = RefactoringUtil.getTypeByExpressionWithExpectedType(myExpression); |
| } |
| } |
| if (expressionType == null) { |
| expressionType = PsiType.VOID; |
| } |
| myHasExpressionOutput = expressionType != PsiType.VOID; |
| |
| PsiType returnStatementType = null; |
| if (myHasReturnStatement) { |
| returnStatementType = myCodeFragmentMember instanceof PsiMethod ? ((PsiMethod)myCodeFragmentMember).getReturnType() : null; |
| } |
| myHasReturnStatementOutput = returnStatementType != null && returnStatementType != PsiType.VOID; |
| |
| if (myGenerateConditionalExit && myOutputVariables.length == 1) { |
| if (!(myOutputVariables[0].getType() instanceof PsiPrimitiveType)) { |
| myNullConditionalCheck = true; |
| for (PsiStatement exitStatement : myExitStatements) { |
| if (exitStatement instanceof PsiReturnStatement) { |
| final PsiExpression returnValue = ((PsiReturnStatement)exitStatement).getReturnValue(); |
| myNullConditionalCheck &= returnValue == null || |
| returnValue instanceof PsiLiteralExpression && PsiType.NULL.equals(returnValue.getType()); |
| } |
| } |
| myNullConditionalCheck &= isNotNull(myOutputVariables[0]); |
| } |
| } |
| |
| if (!myHasReturnStatementOutput && checkOutputVariablesCount() && !myNullConditionalCheck) { |
| showMultipleOutputMessage(expressionType); |
| return false; |
| } |
| |
| myOutputVariable = myOutputVariables.length > 0 ? myOutputVariables[0] : null; |
| if (myHasReturnStatementOutput) { |
| myReturnType = returnStatementType; |
| } |
| else if (myOutputVariable != null) { |
| myReturnType = myOutputVariable.getType(); |
| } |
| else if (myGenerateConditionalExit) { |
| myReturnType = PsiType.BOOLEAN; |
| } |
| else { |
| myReturnType = expressionType; |
| } |
| |
| PsiElement container = PsiTreeUtil.getParentOfType(myElements[0], PsiClass.class, PsiMethod.class); |
| while (container instanceof PsiMethod && ((PsiMethod)container).getContainingClass() != myTargetClass) { |
| container = PsiTreeUtil.getParentOfType(container, PsiMethod.class, true); |
| } |
| if (container instanceof PsiMethod) { |
| PsiElement[] elements = myElements; |
| if (myExpression == null) { |
| if (myOutputVariable != null) { |
| elements = ArrayUtil.append(myElements, myOutputVariable, PsiElement.class); |
| } |
| if (myCodeFragmentMember instanceof PsiMethod && myReturnType == ((PsiMethod)myCodeFragmentMember).getReturnType()) { |
| elements = ArrayUtil.append(myElements, ((PsiMethod)myCodeFragmentMember).getReturnTypeElement(), PsiElement.class); |
| } |
| } |
| myTypeParameterList = RefactoringUtil.createTypeParameterListWithUsedTypeParameters(((PsiMethod)container).getTypeParameterList(), elements); |
| } |
| List<PsiClassType> exceptions = ExceptionUtil.getThrownCheckedExceptions(myElements); |
| myThrownExceptions = exceptions.toArray(new PsiClassType[exceptions.size()]); |
| |
| if (container instanceof PsiMethod) { |
| checkLocalClasses((PsiMethod) container); |
| } |
| return true; |
| } |
| |
| private boolean isNotNull(PsiVariable outputVariable) { |
| final PsiCodeBlock block = myElementFactory.createCodeBlock(); |
| for (PsiElement element : myElements) { |
| block.add(element); |
| } |
| final PsiIfStatement statementFromText = (PsiIfStatement)myElementFactory.createStatementFromText("if (" + outputVariable.getName() + " == null);", null); |
| block.add(statementFromText); |
| |
| final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(block); |
| final StandardInstructionVisitor visitor = new StandardInstructionVisitor(); |
| final RunnerResult rc = dfaRunner.analyzeMethod(block, visitor); |
| if (rc == RunnerResult.OK) { |
| final Set<Instruction> falseSet = dfaRunner.getConstConditionalExpressions().getSecond(); |
| for (Instruction instruction : falseSet) { |
| if (instruction instanceof BranchingInstruction) { |
| if (((BranchingInstruction)instruction).getPsiAnchor().getText().equals(statementFromText.getCondition().getText())) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected boolean checkOutputVariablesCount() { |
| int outputCount = (myHasExpressionOutput ? 1 : 0) + (myGenerateConditionalExit ? 1 : 0) + myOutputVariables.length; |
| return outputCount > 1; |
| } |
| |
| private void checkCanBeChainedConstructor() { |
| if (!(myCodeFragmentMember instanceof PsiMethod)) { |
| return; |
| } |
| final PsiMethod method = (PsiMethod)myCodeFragmentMember; |
| if (!method.isConstructor() || myReturnType != PsiType.VOID) { |
| return; |
| } |
| final PsiCodeBlock body = method.getBody(); |
| if (body == null) return; |
| final PsiStatement[] psiStatements = body.getStatements(); |
| if (psiStatements.length > 0 && myElements [0] == psiStatements [0]) { |
| myCanBeChainedConstructor = true; |
| } |
| } |
| |
| private void checkLocalClasses(final PsiMethod container) throws PrepareFailedException { |
| final List<PsiClass> localClasses = new ArrayList<PsiClass>(); |
| container.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override public void visitClass(final PsiClass aClass) { |
| localClasses.add(aClass); |
| } |
| |
| @Override public void visitAnonymousClass(final PsiAnonymousClass aClass) { |
| visitElement(aClass); |
| } |
| |
| @Override public void visitTypeParameter(final PsiTypeParameter classParameter) { |
| visitElement(classParameter); |
| } |
| }); |
| for(PsiClass localClass: localClasses) { |
| final boolean classExtracted = isExtractedElement(localClass); |
| final List<PsiElement> extractedReferences = Collections.synchronizedList(new ArrayList<PsiElement>()); |
| final List<PsiElement> remainingReferences = Collections.synchronizedList(new ArrayList<PsiElement>()); |
| ReferencesSearch.search(localClass).forEach(new Processor<PsiReference>() { |
| public boolean process(final PsiReference psiReference) { |
| final PsiElement element = psiReference.getElement(); |
| final boolean elementExtracted = isExtractedElement(element); |
| if (elementExtracted && !classExtracted) { |
| extractedReferences.add(element); |
| return false; |
| } |
| if (!elementExtracted && classExtracted) { |
| remainingReferences.add(element); |
| return false; |
| } |
| return true; |
| } |
| }); |
| if (!extractedReferences.isEmpty()) { |
| throw new PrepareFailedException("Cannot extract method because the selected code fragment uses local classes defined outside of the fragment", extractedReferences.get(0)); |
| } |
| if (!remainingReferences.isEmpty()) { |
| throw new PrepareFailedException("Cannot extract method because the selected code fragment defines local classes used outside of the fragment", remainingReferences.get(0)); |
| } |
| if (classExtracted) { |
| for (PsiVariable variable : myControlFlowWrapper.getUsedVariables()) { |
| if (isDeclaredInside(variable) && !variable.equals(myOutputVariable) && PsiUtil.resolveClassInType(variable.getType()) == localClass) { |
| throw new PrepareFailedException("Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment", variable); |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean isExtractedElement(final PsiElement element) { |
| boolean isExtracted = false; |
| for(PsiElement psiElement: myElements) { |
| if (PsiTreeUtil.isAncestor(psiElement, element, false)) { |
| isExtracted = true; |
| break; |
| } |
| } |
| return isExtracted; |
| } |
| |
| |
| |
| private boolean shouldBeStatic() { |
| for(PsiElement element: myElements) { |
| final PsiExpressionStatement statement = PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class); |
| if (statement != null && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) { |
| return true; |
| } |
| } |
| PsiElement codeFragmentMember = myCodeFragmentMember; |
| while (codeFragmentMember != null && PsiTreeUtil.isAncestor(myTargetClass, codeFragmentMember, true)) { |
| if (codeFragmentMember instanceof PsiModifierListOwner && ((PsiModifierListOwner)codeFragmentMember).hasModifierProperty(PsiModifier.STATIC)) { |
| return true; |
| } |
| codeFragmentMember = PsiTreeUtil.getParentOfType(codeFragmentMember, PsiModifierListOwner.class, true); |
| } |
| return false; |
| } |
| |
| public boolean showDialog(final boolean direct) { |
| AbstractExtractDialog dialog = createExtractMethodDialog(direct); |
| dialog.show(); |
| if (!dialog.isOK()) return false; |
| apply(dialog); |
| return true; |
| } |
| |
| protected void apply(final AbstractExtractDialog dialog) { |
| myMethodName = dialog.getChosenMethodName(); |
| myVariableDatum = dialog.getChosenParameters(); |
| myStatic = isStatic() | dialog.isMakeStatic(); |
| myIsChainedConstructor = dialog.isChainedConstructor(); |
| myMethodVisibility = dialog.getVisibility(); |
| } |
| |
| protected AbstractExtractDialog createExtractMethodDialog(final boolean direct) { |
| return new ExtractMethodDialog(myProject, myTargetClass, myInputVariables, myReturnType, getTypeParameterList(), |
| getThrownExceptions(), isStatic(), isCanBeStatic(), myCanBeChainedConstructor, |
| suggestInitialMethodName(), |
| myRefactoringName, myHelpId, myElements) { |
| protected boolean areTypesDirected() { |
| return direct; |
| } |
| |
| @Override |
| protected boolean isOutputVariable(PsiVariable var) { |
| return ExtractMethodProcessor.this.isOutputVariable(var); |
| } |
| }; |
| } |
| |
| protected String suggestInitialMethodName() { |
| if (StringUtil.isEmpty(myInitialMethodName)) { |
| final String initialMethodName; |
| final JavaCodeStyleManagerImpl codeStyleManager = (JavaCodeStyleManagerImpl)JavaCodeStyleManager.getInstance(myProject); |
| final String[] names = codeStyleManager.suggestVariableName(VariableKind.FIELD, null, myExpression, myReturnType).names; |
| if (names.length > 0) { |
| initialMethodName = codeStyleManager.variableNameToPropertyName(names[0], VariableKind.FIELD); |
| } else { |
| return myInitialMethodName; |
| } |
| |
| if (myReturnType != null && !(myReturnType instanceof PsiPrimitiveType)) { |
| return PropertyUtil.suggestGetterName(initialMethodName, myReturnType); |
| } else if (myExpression != null) { |
| if (myExpression instanceof PsiMethodCallExpression) { |
| PsiExpression qualifierExpression = ((PsiMethodCallExpression)myExpression).getMethodExpression().getQualifierExpression(); |
| if (qualifierExpression != null && PsiUtil.resolveGenericsClassInType(qualifierExpression.getType()) != myTargetClass) { |
| return initialMethodName; |
| } |
| } else { |
| return initialMethodName; |
| } |
| } |
| |
| PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(myElements[0], PsiWhiteSpace.class); |
| if (prevSibling instanceof PsiComment && ((PsiComment)prevSibling).getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { |
| final String text = StringUtil.decapitalize(StringUtil.capitalizeWords(prevSibling.getText().trim().substring(2), true)).replaceAll(" ", ""); |
| if (PsiNameHelper.getInstance(myProject).isIdentifier(text) && text.length() < 20) { |
| return text; |
| } |
| } |
| } |
| return myInitialMethodName; |
| } |
| |
| public boolean isOutputVariable(PsiVariable var) { |
| return ArrayUtil.find(myOutputVariables, var) != -1; |
| } |
| |
| public boolean showDialog() { |
| return showDialog(true); |
| } |
| |
| @TestOnly |
| public void testRun() throws IncorrectOperationException { |
| testPrepare(); |
| |
| ExtractMethodHandler.run(myProject, myEditor, this); |
| } |
| |
| @TestOnly |
| public void testPrepare() { |
| myInputVariables.setFoldingAvailable(myInputVariables.isFoldingSelectedByDefault()); |
| myMethodName = myInitialMethodName; |
| myVariableDatum = new VariableData[myInputVariables.getInputVariables().size()]; |
| for (int i = 0; i < myInputVariables.getInputVariables().size(); i++) { |
| myVariableDatum[i] = myInputVariables.getInputVariables().get(i); |
| } |
| } |
| |
| @TestOnly |
| public void doNotPassParameter(int i) { |
| myVariableDatum[i].passAsParameter = false; |
| } |
| |
| /** |
| * Invoked in command and in atomic action |
| */ |
| public void doRefactoring() throws IncorrectOperationException { |
| List<PsiElement> elements = new ArrayList<PsiElement>(); |
| for (PsiElement element : myElements) { |
| if (!(element instanceof PsiWhiteSpace || element instanceof PsiComment)) { |
| elements.add(element); |
| } |
| } |
| if (myExpression != null) { |
| myDuplicatesFinder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), |
| new ArrayList<PsiVariable>()); |
| myDuplicates = myDuplicatesFinder.findDuplicates(myTargetClass); |
| } |
| else if (elements.size() > 0){ |
| myDuplicatesFinder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), |
| myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null, |
| Arrays.asList(myOutputVariables)); |
| myDuplicates = myDuplicatesFinder.findDuplicates(myTargetClass); |
| } else { |
| myDuplicates = new ArrayList<Match>(); |
| } |
| |
| chooseAnchor(); |
| |
| LogicalPosition pos1; |
| if (myEditor != null) { |
| int col = myEditor.getCaretModel().getLogicalPosition().column; |
| int line = myEditor.getCaretModel().getLogicalPosition().line; |
| pos1 = new LogicalPosition(line, col); |
| LogicalPosition pos = new LogicalPosition(0, 0); |
| myEditor.getCaretModel().moveToLogicalPosition(pos); |
| } else { |
| pos1 = null; |
| } |
| |
| final SearchScope processConflictsScope = myMethodVisibility.equals(PsiModifier.PRIVATE) ? |
| new LocalSearchScope(myTargetClass) : |
| GlobalSearchScope.projectScope(myProject); |
| |
| final Map<PsiMethodCallExpression, PsiMethod> overloadsResolveMap = new HashMap<PsiMethodCallExpression, PsiMethod>(); |
| final Runnable collectOverloads = new Runnable() { |
| public void run() { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| Map<PsiMethodCallExpression, PsiMethod> overloads = |
| ExtractMethodUtil.encodeOverloadTargets(myTargetClass, processConflictsScope, myMethodName, myCodeFragmentMember); |
| overloadsResolveMap.putAll(overloads); |
| } |
| }); |
| } |
| }; |
| final Runnable extract = new Runnable() { |
| public void run() { |
| doExtract(); |
| ExtractMethodUtil.decodeOverloadTargets(overloadsResolveMap, myExtractedMethod, myCodeFragmentMember); |
| } |
| }; |
| if (ApplicationManager.getApplication().isWriteAccessAllowed()) { |
| collectOverloads.run(); |
| extract.run(); |
| } else { |
| if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(collectOverloads, "Collect overloads...", true, myProject)) return; |
| ApplicationManager.getApplication().runWriteAction(extract); |
| } |
| |
| if (myEditor != null) { |
| myEditor.getCaretModel().moveToLogicalPosition(pos1); |
| int offset = myMethodCall.getMethodExpression().getTextRange().getStartOffset(); |
| myEditor.getCaretModel().moveToOffset(offset); |
| myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); |
| myEditor.getSelectionModel().removeSelection(); |
| } |
| } |
| |
| public void doExtract() throws IncorrectOperationException { |
| |
| PsiMethod newMethod = generateEmptyMethod(getThrownExceptions(), isStatic()); |
| |
| myExpression = myInputVariables.replaceWrappedReferences(myElements, myExpression); |
| renameInputVariables(); |
| |
| LOG.assertTrue(myElements[0].isValid()); |
| |
| PsiCodeBlock body = newMethod.getBody(); |
| myMethodCall = generateMethodCall(null, true); |
| |
| LOG.assertTrue(myElements[0].isValid()); |
| |
| if (myExpression == null) { |
| String outVariableName = myOutputVariable != null ? getNewVariableName(myOutputVariable) : null; |
| PsiReturnStatement returnStatement; |
| if (myNullConditionalCheck) { |
| returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return null;", null); |
| } else if (myOutputVariable != null) { |
| returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return " + outVariableName + ";", null); |
| } |
| else if (myGenerateConditionalExit) { |
| returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return true;", null); |
| } |
| else { |
| returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return;", null); |
| } |
| |
| boolean hasNormalExit = false; |
| PsiElement lastElement = myElements[myElements.length - 1]; |
| if (!(lastElement instanceof PsiReturnStatement || lastElement instanceof PsiBreakStatement || |
| lastElement instanceof PsiContinueStatement)) { |
| hasNormalExit = true; |
| } |
| |
| PsiStatement exitStatementCopy = myControlFlowWrapper.getExitStatementCopy(returnStatement, myElements); |
| |
| |
| declareNecessaryVariablesInsideBody(body); |
| |
| |
| |
| if (myNeedChangeContext) { |
| for (PsiElement element : myElements) { |
| ChangeContextUtil.encodeContextInfo(element, false); |
| } |
| } |
| |
| body.addRange(myElements[0], myElements[myElements.length - 1]); |
| if (myNullConditionalCheck) { |
| body.add(myElementFactory.createStatementFromText("return " + myOutputVariable.getName() + ";", null)); |
| } else if (myGenerateConditionalExit) { |
| body.add(myElementFactory.createStatementFromText("return false;", null)); |
| } |
| else if (!myHasReturnStatement && hasNormalExit && myOutputVariable != null) { |
| final PsiReturnStatement insertedReturnStatement = (PsiReturnStatement)body.add(returnStatement); |
| if (myOutputVariables.length == 1) { |
| final PsiExpression returnValue = insertedReturnStatement.getReturnValue(); |
| if (returnValue instanceof PsiReferenceExpression) { |
| final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve(); |
| if (resolved instanceof PsiLocalVariable && Comparing.strEqual(((PsiVariable)resolved).getName(), outVariableName)) { |
| final PsiStatement statement = PsiTreeUtil.getPrevSiblingOfType(insertedReturnStatement, PsiStatement.class); |
| if (statement instanceof PsiDeclarationStatement) { |
| final PsiElement[] declaredElements = ((PsiDeclarationStatement)statement).getDeclaredElements(); |
| if (ArrayUtil.find(declaredElements, resolved) != -1) { |
| InlineUtil.inlineVariable((PsiVariable)resolved, ((PsiVariable)resolved).getInitializer(), (PsiReferenceExpression)returnValue); |
| resolved.delete(); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (myNullConditionalCheck) { |
| final String varName = myOutputVariable.getName(); |
| if (isDeclaredInside(myOutputVariable)) { |
| declareVariableAtMethodCallLocation(varName); |
| } |
| else { |
| PsiExpressionStatement assignmentExpression = |
| (PsiExpressionStatement)myElementFactory.createStatementFromText(varName + "=x;", null); |
| assignmentExpression = (PsiExpressionStatement)addToMethodCallLocation(assignmentExpression); |
| myMethodCall = |
| (PsiMethodCallExpression)((PsiAssignmentExpression)assignmentExpression.getExpression()).getRExpression().replace(myMethodCall); |
| } |
| declareNecessaryVariablesAfterCall(myOutputVariable); |
| PsiIfStatement ifStatement = |
| (PsiIfStatement)myElementFactory.createStatementFromText(myHasReturnStatementOutput || (myGenerateConditionalExit && myFirstExitStatementCopy instanceof PsiReturnStatement && |
| ((PsiReturnStatement)myFirstExitStatementCopy).getReturnValue() != null) |
| ? "if (" + varName + "==null) return null;" |
| : "if (" + varName + "==null) return;", null); |
| ifStatement = (PsiIfStatement)addToMethodCallLocation(ifStatement); |
| CodeStyleManager.getInstance(myProject).reformat(ifStatement); |
| } |
| else if (myGenerateConditionalExit) { |
| PsiIfStatement ifStatement = (PsiIfStatement)myElementFactory.createStatementFromText("if (a) b;", null); |
| ifStatement = (PsiIfStatement)addToMethodCallLocation(ifStatement); |
| myMethodCall = (PsiMethodCallExpression)ifStatement.getCondition().replace(myMethodCall); |
| myFirstExitStatementCopy = (PsiStatement)ifStatement.getThenBranch().replace(myFirstExitStatementCopy); |
| CodeStyleManager.getInstance(myProject).reformat(ifStatement); |
| } |
| else if (myOutputVariable != null) { |
| String name = myOutputVariable.getName(); |
| boolean toDeclare = isDeclaredInside(myOutputVariable); |
| if (!toDeclare) { |
| PsiExpressionStatement statement = (PsiExpressionStatement)myElementFactory.createStatementFromText(name + "=x;", null); |
| statement = (PsiExpressionStatement)myStyleManager.reformat(statement); |
| statement = (PsiExpressionStatement)addToMethodCallLocation(statement); |
| PsiAssignmentExpression assignment = (PsiAssignmentExpression)statement.getExpression(); |
| myMethodCall = (PsiMethodCallExpression)assignment.getRExpression().replace(myMethodCall); |
| } |
| else { |
| declareVariableAtMethodCallLocation(name); |
| } |
| } |
| else if (myHasReturnStatementOutput) { |
| PsiStatement statement = myElementFactory.createStatementFromText("return x;", null); |
| statement = (PsiStatement)addToMethodCallLocation(statement); |
| myMethodCall = (PsiMethodCallExpression)((PsiReturnStatement)statement).getReturnValue().replace(myMethodCall); |
| } |
| else { |
| PsiStatement statement = myElementFactory.createStatementFromText("x();", null); |
| statement = (PsiStatement)addToMethodCallLocation(statement); |
| myMethodCall = (PsiMethodCallExpression)((PsiExpressionStatement)statement).getExpression().replace(myMethodCall); |
| } |
| if (myHasReturnStatement && !myHasReturnStatementOutput && !hasNormalExit) { |
| PsiStatement statement = myElementFactory.createStatementFromText("return;", null); |
| addToMethodCallLocation(statement); |
| } |
| else if (!myGenerateConditionalExit && exitStatementCopy != null) { |
| addToMethodCallLocation(exitStatementCopy); |
| } |
| |
| if (!myNullConditionalCheck) { |
| declareNecessaryVariablesAfterCall(myOutputVariable); |
| } |
| |
| deleteExtracted(); |
| } |
| else { |
| declareNecessaryVariablesInsideBody(body); |
| if (myHasExpressionOutput) { |
| PsiReturnStatement returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return x;", null); |
| final PsiExpression returnValue = RefactoringUtil.convertInitializerToNormalExpression(myExpression, myForcedReturnType); |
| returnStatement.getReturnValue().replace(returnValue); |
| body.add(returnStatement); |
| } |
| else { |
| PsiExpressionStatement statement = (PsiExpressionStatement)myElementFactory.createStatementFromText("x;", null); |
| statement.getExpression().replace(myExpression); |
| body.add(statement); |
| } |
| PsiExpression expression2Replace = myExpression; |
| if (myExpression instanceof PsiAssignmentExpression) { |
| expression2Replace = ((PsiAssignmentExpression)myExpression).getRExpression(); |
| } else if (myExpression instanceof PsiPostfixExpression || myExpression instanceof PsiPrefixExpression) { |
| final IElementType elementType = myExpression instanceof PsiPostfixExpression |
| ? ((PsiPostfixExpression)myExpression).getOperationTokenType() |
| : ((PsiPrefixExpression)myExpression).getOperationTokenType(); |
| if (elementType == JavaTokenType.PLUSPLUS || elementType == JavaTokenType.MINUSMINUS) { |
| PsiExpression operand = myExpression instanceof PsiPostfixExpression ? ((PsiPostfixExpression)myExpression).getOperand() : |
| ((PsiPrefixExpression)myExpression).getOperand(); |
| expression2Replace = |
| ((PsiBinaryExpression)myExpression.replace(myElementFactory.createExpressionFromText(operand.getText() + " + x", operand))).getROperand(); |
| } |
| |
| } |
| myExpression = (PsiExpression)IntroduceVariableBase.replace(expression2Replace, myMethodCall, myProject); |
| myMethodCall = PsiTreeUtil.getParentOfType(myExpression.findElementAt(myExpression.getText().indexOf(myMethodCall.getText())), PsiMethodCallExpression.class); |
| declareNecessaryVariablesAfterCall(myOutputVariable); |
| } |
| |
| if (myAnchor instanceof PsiField) { |
| ((PsiField)myAnchor).normalizeDeclaration(); |
| } |
| |
| adjustFinalParameters(newMethod); |
| int i = 0; |
| for (VariableData data : myVariableDatum) { |
| if (!data.passAsParameter) continue; |
| final PsiParameter psiParameter = newMethod.getParameterList().getParameters()[i++]; |
| final PsiType paramType = psiParameter.getType(); |
| for (PsiReference reference : ReferencesSearch.search(psiParameter, new LocalSearchScope(body))){ |
| final PsiElement element = reference.getElement(); |
| if (element != null) { |
| final PsiElement parent = element.getParent(); |
| if (parent instanceof PsiTypeCastExpression) { |
| final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)parent; |
| final PsiTypeElement castType = typeCastExpression.getCastType(); |
| if (castType != null && Comparing.equal(castType.getType(), paramType)) { |
| RedundantCastUtil.removeCast(typeCastExpression); |
| } |
| } |
| } |
| } |
| } |
| |
| myExtractedMethod = (PsiMethod)myTargetClass.addAfter(newMethod, myAnchor); |
| if (isNeedToChangeCallContext() && myNeedChangeContext) { |
| ChangeContextUtil.decodeContextInfo(myExtractedMethod, myTargetClass, RefactoringChangeUtil.createThisExpression(myManager, null)); |
| if (myMethodCall.resolveMethod() != myExtractedMethod) { |
| final PsiReferenceExpression methodExpression = myMethodCall.getMethodExpression(); |
| methodExpression.setQualifierExpression(RefactoringChangeUtil.createThisExpression(myManager, myTargetClass)); |
| } |
| } |
| |
| } |
| |
| protected boolean isNeedToChangeCallContext() { |
| return true; |
| } |
| |
| private void declareVariableAtMethodCallLocation(String name) { |
| PsiDeclarationStatement statement = |
| myElementFactory.createVariableDeclarationStatement(name, myOutputVariable.getType(), myMethodCall); |
| statement = (PsiDeclarationStatement)addToMethodCallLocation(statement); |
| PsiVariable var = (PsiVariable)statement.getDeclaredElements()[0]; |
| myMethodCall = (PsiMethodCallExpression)var.getInitializer(); |
| var.getModifierList().replace(myOutputVariable.getModifierList()); |
| } |
| |
| private void adjustFinalParameters(final PsiMethod method) throws IncorrectOperationException { |
| final IncorrectOperationException[] exc = new IncorrectOperationException[1]; |
| exc[0] = null; |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| if (parameters.length > 0) { |
| if (CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS) { |
| method.accept(new JavaRecursiveElementVisitor() { |
| |
| @Override public void visitReferenceExpression(PsiReferenceExpression expression) { |
| final PsiElement resolved = expression.resolve(); |
| if (resolved != null) { |
| final int index = ArrayUtil.find(parameters, resolved); |
| if (index >= 0) { |
| final PsiParameter param = parameters[index]; |
| if (param.hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isAccessedForWriting(expression)) { |
| try { |
| PsiUtil.setModifierProperty(param, PsiModifier.FINAL, false); |
| } |
| catch (IncorrectOperationException e) { |
| exc[0] = e; |
| } |
| } |
| } |
| } |
| super.visitReferenceExpression(expression); |
| } |
| }); |
| } |
| else { |
| method.accept(new JavaRecursiveElementVisitor() { |
| @Override public void visitReferenceExpression(PsiReferenceExpression expression) { |
| final PsiElement resolved = expression.resolve(); |
| final int index = ArrayUtil.find(parameters, resolved); |
| if (index >= 0) { |
| final PsiParameter param = parameters[index]; |
| if (!param.hasModifierProperty(PsiModifier.FINAL) && RefactoringUtil.isInsideAnonymousOrLocal(expression, method)) { |
| try { |
| PsiUtil.setModifierProperty(param, PsiModifier.FINAL, true); |
| } |
| catch (IncorrectOperationException e) { |
| exc[0] = e; |
| } |
| } |
| } |
| super.visitReferenceExpression(expression); |
| } |
| }); |
| } |
| if (exc[0] != null) { |
| throw exc[0]; |
| } |
| } |
| } |
| |
| public List<Match> getDuplicates() { |
| if (myIsChainedConstructor) { |
| return filterChainedConstructorDuplicates(myDuplicates); |
| } |
| return myDuplicates; |
| } |
| |
| private static List<Match> filterChainedConstructorDuplicates(final List<Match> duplicates) { |
| List<Match> result = new ArrayList<Match>(); |
| for(Match duplicate: duplicates) { |
| final PsiElement matchStart = duplicate.getMatchStart(); |
| final PsiMethod method = PsiTreeUtil.getParentOfType(matchStart, PsiMethod.class); |
| if (method != null && method.isConstructor()) { |
| final PsiCodeBlock body = method.getBody(); |
| if (body != null) { |
| final PsiStatement[] psiStatements = body.getStatements(); |
| if (psiStatements.length > 0 && matchStart == psiStatements [0]) { |
| result.add(duplicate); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| public PsiElement processMatch(Match match) throws IncorrectOperationException { |
| MatchUtil.changeSignature(match, myExtractedMethod); |
| if (RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass())) { |
| PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.STATIC, true); |
| } |
| final PsiMethodCallExpression methodCallExpression = generateMethodCall(match.getInstanceExpression(), false); |
| |
| ArrayList<VariableData> datas = new ArrayList<VariableData>(); |
| for (final VariableData variableData : myVariableDatum) { |
| if (variableData.passAsParameter) { |
| datas.add(variableData); |
| } |
| } |
| final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); |
| for (VariableData data : datas) { |
| final List<PsiElement> parameterValue = match.getParameterValues(data.variable); |
| if (parameterValue != null) { |
| for (PsiElement val : parameterValue) { |
| if (val instanceof PsiExpression) { |
| final PsiType exprType = ((PsiExpression)val).getType(); |
| if (exprType != null && !TypeConversionUtil.isAssignable(data.type, exprType)) { |
| final PsiTypeCastExpression cast = (PsiTypeCastExpression)elementFactory.createExpressionFromText("(A)a", val); |
| cast.getCastType().replace(elementFactory.createTypeElement(data.type)); |
| cast.getOperand().replace(val.copy()); |
| val = cast; |
| } |
| } |
| methodCallExpression.getArgumentList().add(val); |
| } |
| } |
| } |
| return match.replace(myExtractedMethod, methodCallExpression, myOutputVariable); |
| } |
| |
| private void deleteExtracted() throws IncorrectOperationException { |
| if (myEnclosingBlockStatement == null) { |
| myElements[0].getParent().deleteChildRange(myElements[0], myElements[myElements.length - 1]); |
| } |
| else { |
| myEnclosingBlockStatement.delete(); |
| } |
| } |
| |
| protected PsiElement addToMethodCallLocation(PsiStatement statement) throws IncorrectOperationException { |
| if (myEnclosingBlockStatement == null) { |
| PsiElement containingStatement = myElements[0] instanceof PsiComment ? myElements[0] : PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiStatement.class, false); |
| if (containingStatement == null) { |
| containingStatement = PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiComment.class, false); |
| } |
| |
| return containingStatement.getParent().addBefore(statement, containingStatement); |
| } |
| else { |
| return myEnclosingBlockStatement.getParent().addBefore(statement, myEnclosingBlockStatement); |
| } |
| } |
| |
| private void renameInputVariables() throws IncorrectOperationException { |
| for (VariableData data : myVariableDatum) { |
| PsiVariable variable = data.variable; |
| if (!data.name.equals(variable.getName())) { |
| for (PsiElement element : myElements) { |
| RefactoringUtil.renameVariableReferences(variable, data.name, new LocalSearchScope(element)); |
| } |
| } |
| } |
| } |
| |
| public PsiClass getTargetClass() { |
| return myTargetClass; |
| } |
| |
| public PsiType getReturnType() { |
| return myReturnType; |
| } |
| |
| private PsiMethod generateEmptyMethod(PsiClassType[] exceptions, boolean isStatic) throws IncorrectOperationException { |
| PsiMethod newMethod; |
| if (myIsChainedConstructor) { |
| newMethod = myElementFactory.createConstructor(); |
| } |
| else { |
| newMethod = myElementFactory.createMethod(myMethodName, myReturnType); |
| PsiUtil.setModifierProperty(newMethod, PsiModifier.STATIC, isStatic); |
| } |
| PsiUtil.setModifierProperty(newMethod, myMethodVisibility, true); |
| if (getTypeParameterList() != null) { |
| newMethod.getTypeParameterList().replace(getTypeParameterList()); |
| } |
| PsiCodeBlock body = newMethod.getBody(); |
| LOG.assertTrue(body != null); |
| |
| boolean isFinal = CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS; |
| PsiParameterList list = newMethod.getParameterList(); |
| for (VariableData data : myVariableDatum) { |
| if (data.passAsParameter) { |
| PsiParameter parm = myElementFactory.createParameter(data.name, data.type); |
| if (isFinal) { |
| PsiUtil.setModifierProperty(parm, PsiModifier.FINAL, true); |
| } |
| list.add(parm); |
| } |
| else { |
| @NonNls StringBuilder buffer = new StringBuilder(); |
| if (isFinal) { |
| buffer.append("final "); |
| } |
| buffer.append("int "); |
| buffer.append(data.name); |
| buffer.append("=;"); |
| String text = buffer.toString(); |
| |
| PsiDeclarationStatement declaration = (PsiDeclarationStatement)myElementFactory.createStatementFromText(text, null); |
| declaration = (PsiDeclarationStatement)myStyleManager.reformat(declaration); |
| final PsiTypeElement typeElement = myElementFactory.createTypeElement(data.type); |
| ((PsiVariable)declaration.getDeclaredElements()[0]).getTypeElement().replace(typeElement); |
| body.add(declaration); |
| } |
| } |
| |
| PsiReferenceList throwsList = newMethod.getThrowsList(); |
| for (PsiClassType exception : exceptions) { |
| throwsList.add(JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createReferenceElementByType(exception)); |
| } |
| |
| if (myNullConditionalCheck) { |
| final boolean isNullCheckReturnNull = (myHasExpressionOutput ? 1 : 0) + (myGenerateConditionalExit ? 1 : 0) + myOutputVariables.length <= 1; |
| if (isNullCheckReturnNull && PsiUtil.isLanguageLevel5OrHigher(myElements[0])) { |
| final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); |
| final PsiClass nullableAnnotationClass = |
| JavaPsiFacade.getInstance(myProject).findClass(manager.getDefaultNullable(), GlobalSearchScope.allScope(myProject)); |
| if (nullableAnnotationClass != null) { |
| new AddNullableAnnotationFix(newMethod).invoke(myProject, myTargetClass.getContainingFile(), newMethod, newMethod); |
| } |
| } |
| } |
| |
| if (myTargetClass.isInterface() && PsiUtil.isLanguageLevel8OrHigher(myTargetClass)) { |
| PsiUtil.setModifierProperty(newMethod, PsiModifier.DEFAULT, true); |
| } |
| return (PsiMethod)myStyleManager.reformat(newMethod); |
| } |
| |
| @NotNull |
| protected PsiMethodCallExpression generateMethodCall(PsiExpression instanceQualifier, final boolean generateArgs) throws IncorrectOperationException { |
| @NonNls StringBuilder buffer = new StringBuilder(); |
| |
| final boolean skipInstanceQualifier; |
| if (myIsChainedConstructor) { |
| skipInstanceQualifier = true; |
| buffer.append(PsiKeyword.THIS); |
| } |
| else { |
| skipInstanceQualifier = instanceQualifier == null || instanceQualifier instanceof PsiThisExpression; |
| if (skipInstanceQualifier) { |
| if (myNeedChangeContext) { |
| boolean needsThisQualifier = false; |
| PsiElement parent = myCodeFragmentMember; |
| while (!myTargetClass.equals(parent)) { |
| if (parent instanceof PsiMethod) { |
| String methodName = ((PsiMethod)parent).getName(); |
| if (methodName.equals(myMethodName)) { |
| needsThisQualifier = true; |
| break; |
| } |
| } |
| parent = parent.getParent(); |
| } |
| if (needsThisQualifier) { |
| buffer.append(myTargetClass.getName()); |
| buffer.append(".this."); |
| } |
| } |
| } |
| else { |
| buffer.append("qqq."); |
| } |
| |
| buffer.append(myMethodName); |
| } |
| buffer.append("("); |
| if (generateArgs) { |
| int count = 0; |
| for (VariableData data : myVariableDatum) { |
| if (data.passAsParameter) { |
| if (count > 0) { |
| buffer.append(","); |
| } |
| myInputVariables.appendCallArguments(data, buffer); |
| count++; |
| } |
| } |
| } |
| buffer.append(")"); |
| String text = buffer.toString(); |
| |
| PsiMethodCallExpression expr = (PsiMethodCallExpression)myElementFactory.createExpressionFromText(text, null); |
| expr = (PsiMethodCallExpression)myStyleManager.reformat(expr); |
| if (!skipInstanceQualifier) { |
| PsiExpression qualifierExpression = expr.getMethodExpression().getQualifierExpression(); |
| LOG.assertTrue(qualifierExpression != null); |
| qualifierExpression.replace(instanceQualifier); |
| } |
| return (PsiMethodCallExpression)JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(expr); |
| } |
| |
| private boolean chooseTargetClass(PsiElement codeFragment, final Pass<ExtractMethodProcessor> extractPass) throws PrepareFailedException { |
| final List<PsiVariable> inputVariables = myControlFlowWrapper.getInputVariables(codeFragment, myElements); |
| |
| myNeedChangeContext = false; |
| myTargetClass = myCodeFragmentMember instanceof PsiMember |
| ? ((PsiMember)myCodeFragmentMember).getContainingClass() |
| : PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiClass.class); |
| if (!shouldAcceptCurrentTarget(extractPass, myTargetClass)) { |
| |
| final LinkedHashMap<PsiClass, List<PsiVariable>> classes = new LinkedHashMap<PsiClass, List<PsiVariable>>(); |
| final PsiElementProcessor<PsiClass> processor = new PsiElementProcessor<PsiClass>() { |
| @Override |
| public boolean execute(@NotNull PsiClass selectedClass) { |
| AnonymousTargetClassPreselectionUtil.rememberSelection(selectedClass, myTargetClass); |
| final List<PsiVariable> array = classes.get(selectedClass); |
| myNeedChangeContext = myTargetClass != selectedClass; |
| myTargetClass = selectedClass; |
| if (array != null) { |
| for (PsiVariable variable : array) { |
| if (!inputVariables.contains(variable)) { |
| inputVariables.addAll(array); |
| } |
| } |
| } |
| try { |
| return applyChosenClassAndExtract(inputVariables, extractPass); |
| } |
| catch (PrepareFailedException e) { |
| if (myShowErrorDialogs) { |
| CommonRefactoringUtil |
| .showErrorHint(myProject, myEditor, e.getMessage(), ExtractMethodHandler.REFACTORING_NAME, HelpID.EXTRACT_METHOD); |
| ExtractMethodHandler.highlightPrepareError(e, e.getFile(), myEditor, myProject); |
| } |
| return false; |
| } |
| } |
| }; |
| |
| classes.put(myTargetClass, null); |
| PsiElement target = myTargetClass.getParent(); |
| PsiElement targetMember = myTargetClass; |
| while (true) { |
| if (target instanceof PsiFile) break; |
| if (target instanceof PsiClass) { |
| boolean success = true; |
| final List<PsiVariable> array = new ArrayList<PsiVariable>(); |
| for (PsiElement el : myElements) { |
| if (!ControlFlowUtil.collectOuterLocals(array, el, myCodeFragmentMember, targetMember)) { |
| success = false; |
| break; |
| } |
| } |
| if (success) { |
| classes.put((PsiClass)target, array); |
| if (shouldAcceptCurrentTarget(extractPass, target)) { |
| return processor.execute((PsiClass)target); |
| } |
| } |
| } |
| targetMember = target; |
| target = target.getParent(); |
| } |
| |
| if (classes.size() > 1) { |
| final PsiClass[] psiClasses = classes.keySet().toArray(new PsiClass[classes.size()]); |
| final PsiClass preselection = AnonymousTargetClassPreselectionUtil.getPreselection(classes.keySet(), psiClasses[0]); |
| NavigationUtil.getPsiElementPopup(psiClasses, new PsiClassListCellRenderer(), "Choose Destination Class", processor, preselection) |
| .showInBestPositionFor(myEditor); |
| return true; |
| } |
| } |
| |
| return applyChosenClassAndExtract(inputVariables, extractPass); |
| } |
| |
| private void declareNecessaryVariablesInsideBody(PsiCodeBlock body) throws IncorrectOperationException { |
| List<PsiVariable> usedVariables = myControlFlowWrapper.getUsedVariablesInBody(); |
| for (PsiVariable variable : usedVariables) { |
| boolean toDeclare = !isDeclaredInside(variable) && myInputVariables.toDeclareInsideBody(variable); |
| if (toDeclare) { |
| String name = variable.getName(); |
| PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); |
| body.add(statement); |
| } |
| } |
| } |
| |
| protected void declareNecessaryVariablesAfterCall(PsiVariable outputVariable) throws IncorrectOperationException { |
| if (myHasExpressionOutput) return; |
| List<PsiVariable> usedVariables = myControlFlowWrapper.getUsedVariables(); |
| Collection<ControlFlowUtil.VariableInfo> reassigned = myControlFlowWrapper.getInitializedTwice(); |
| for (PsiVariable variable : usedVariables) { |
| boolean toDeclare = isDeclaredInside(variable) && !variable.equals(outputVariable); |
| if (toDeclare) { |
| String name = variable.getName(); |
| PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); |
| if (reassigned.contains(new ControlFlowUtil.VariableInfo(variable, null))) { |
| final PsiElement[] psiElements = statement.getDeclaredElements(); |
| assert psiElements.length > 0; |
| PsiVariable var = (PsiVariable) psiElements [0]; |
| PsiUtil.setModifierProperty(var, PsiModifier.FINAL, false); |
| } |
| addToMethodCallLocation(statement); |
| } |
| } |
| } |
| |
| public PsiMethodCallExpression getMethodCall() { |
| return myMethodCall; |
| } |
| |
| public void setMethodCall(PsiMethodCallExpression methodCall) { |
| myMethodCall = methodCall; |
| } |
| |
| public boolean isDeclaredInside(PsiVariable variable) { |
| if (variable instanceof ImplicitVariable) return false; |
| int startOffset; |
| int endOffset; |
| if (myExpression != null) { |
| final TextRange range = myExpression.getTextRange(); |
| startOffset = range.getStartOffset(); |
| endOffset = range.getEndOffset(); |
| } else { |
| startOffset = myElements[0].getTextRange().getStartOffset(); |
| endOffset = myElements[myElements.length - 1].getTextRange().getEndOffset(); |
| } |
| PsiIdentifier nameIdentifier = variable.getNameIdentifier(); |
| if (nameIdentifier == null) return false; |
| final TextRange range = nameIdentifier.getTextRange(); |
| if (range == null) return false; |
| int offset = range.getStartOffset(); |
| return startOffset <= offset && offset <= endOffset; |
| } |
| |
| private String getNewVariableName(PsiVariable variable) { |
| for (VariableData data : myVariableDatum) { |
| if (data.variable.equals(variable)) { |
| return data.name; |
| } |
| } |
| return variable.getName(); |
| } |
| |
| private static boolean shouldAcceptCurrentTarget(Pass<ExtractMethodProcessor> extractPass, PsiElement target) { |
| return extractPass == null && !(target instanceof PsiAnonymousClass); |
| } |
| |
| private boolean applyChosenClassAndExtract(List<PsiVariable> inputVariables, @Nullable Pass<ExtractMethodProcessor> extractPass) |
| throws PrepareFailedException { |
| myStatic = shouldBeStatic(); |
| if (!PsiUtil.isLocalOrAnonymousClass(myTargetClass) && (myTargetClass.getContainingClass() == null || myTargetClass.hasModifierProperty(PsiModifier.STATIC))) { |
| ElementNeedsThis needsThis = new ElementNeedsThis(myTargetClass); |
| for (int i = 0; i < myElements.length && !needsThis.usesMembers(); i++) { |
| PsiElement element = myElements[i]; |
| element.accept(needsThis); |
| } |
| myCanBeStatic = !needsThis.usesMembers(); |
| } |
| else { |
| myCanBeStatic = false; |
| } |
| |
| myInputVariables = new InputVariables(inputVariables, myProject, new LocalSearchScope(myElements), true); |
| |
| if (!checkExitPoints()){ |
| return false; |
| } |
| |
| checkCanBeChainedConstructor(); |
| |
| if (extractPass != null) { |
| extractPass.pass(this); |
| } |
| return true; |
| } |
| |
| private void chooseAnchor() { |
| myAnchor = myCodeFragmentMember; |
| while (!myAnchor.getParent().equals(myTargetClass)) { |
| myAnchor = myAnchor.getParent(); |
| } |
| } |
| |
| private void showMultipleExitPointsMessage() { |
| if (myShowErrorDialogs) { |
| HighlightManager highlightManager = HighlightManager.getInstance(myProject); |
| PsiStatement[] exitStatementsArray = myExitStatements.toArray(new PsiStatement[myExitStatements.size()]); |
| EditorColorsManager manager = EditorColorsManager.getInstance(); |
| TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); |
| highlightManager.addOccurrenceHighlights(myEditor, exitStatementsArray, attributes, true, null); |
| String message = RefactoringBundle |
| .getCannotRefactorMessage(RefactoringBundle.message("there.are.multiple.exit.points.in.the.selected.code.fragment")); |
| CommonRefactoringUtil.showErrorHint(myProject, myEditor, message, myRefactoringName, myHelpId); |
| WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); |
| } |
| } |
| |
| private void showMultipleOutputMessage(PsiType expressionType) { |
| if (myShowErrorDialogs) { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append(RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("there.are.multiple.output.values.for.the.selected.code.fragment"))); |
| buffer.append("\n"); |
| if (myHasExpressionOutput) { |
| buffer.append(" ").append(RefactoringBundle.message("expression.result")).append(": "); |
| buffer.append(PsiFormatUtil.formatType(expressionType, 0, PsiSubstitutor.EMPTY)); |
| buffer.append(",\n"); |
| } |
| if (myGenerateConditionalExit) { |
| buffer.append(" ").append(RefactoringBundle.message("boolean.method.result")); |
| buffer.append(",\n"); |
| } |
| for (int i = 0; i < myOutputVariables.length; i++) { |
| PsiVariable var = myOutputVariables[i]; |
| buffer.append(" "); |
| buffer.append(var.getName()); |
| buffer.append(" : "); |
| buffer.append(PsiFormatUtil.formatType(var.getType(), 0, PsiSubstitutor.EMPTY)); |
| if (i < myOutputVariables.length - 1) { |
| buffer.append(",\n"); |
| } |
| else { |
| buffer.append("."); |
| } |
| } |
| buffer.append("\nWould you like to Extract Method Object?"); |
| |
| String message = buffer.toString(); |
| |
| if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(message); |
| RefactoringMessageDialog dialog = new RefactoringMessageDialog(myRefactoringName, message, myHelpId, "OptionPane.errorIcon", true, |
| myProject); |
| dialog.show(); |
| if (dialog.isOK()) { |
| new ExtractMethodObjectHandler().invoke(myProject, myEditor, myTargetClass.getContainingFile(), DataManager.getInstance().getDataContext()); |
| } |
| } |
| } |
| |
| public PsiMethod getExtractedMethod() { |
| return myExtractedMethod; |
| } |
| |
| public boolean hasDuplicates() { |
| final List<Match> duplicates = getDuplicates(); |
| return duplicates != null && !duplicates.isEmpty(); |
| } |
| |
| public boolean hasDuplicates(Set<VirtualFile> files) { |
| if (hasDuplicates()) return true; |
| final PsiManager psiManager = PsiManager.getInstance(myProject); |
| for (VirtualFile file : files) { |
| if (myDuplicatesFinder != null && !myDuplicatesFinder.findDuplicates(psiManager.findFile(file)).isEmpty()) return true; |
| } |
| return false; |
| } |
| |
| @Nullable |
| public String getConfirmDuplicatePrompt(Match match) { |
| final boolean needToBeStatic = RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass()); |
| final String changedSignature = MatchUtil |
| .getChangedSignature(match, myExtractedMethod, needToBeStatic, VisibilityUtil.getVisibilityStringToDisplay(myExtractedMethod)); |
| if (changedSignature != null) { |
| return RefactoringBundle.message("replace.this.code.fragment.and.change.signature", changedSignature); |
| } |
| if (needToBeStatic && !myExtractedMethod.hasModifierProperty(PsiModifier.STATIC)) { |
| return RefactoringBundle.message("replace.this.code.fragment.and.make.method.static"); |
| } |
| return null; |
| } |
| |
| @Override |
| public String getReplaceDuplicatesTitle(int idx, int size) { |
| return RefactoringBundle.message("process.duplicates.title", idx, size); |
| } |
| |
| public InputVariables getInputVariables() { |
| return myInputVariables; |
| } |
| |
| public PsiTypeParameterList getTypeParameterList() { |
| return myTypeParameterList; |
| } |
| |
| public PsiClassType[] getThrownExceptions() { |
| return myThrownExceptions; |
| } |
| |
| public boolean isStatic() { |
| return myStatic; |
| } |
| |
| public boolean isCanBeStatic() { |
| return myCanBeStatic; |
| } |
| |
| public PsiElement[] getElements() { |
| return myElements; |
| } |
| |
| public PsiVariable[] getOutputVariables() { |
| return myOutputVariables; |
| } |
| } |