Java tutorial
/* * Copyright 2000-2009 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. */ /* * Created by IntelliJ IDEA. * User: dsl * Date: 06.05.2002 * Time: 13:36:30 * To change template for new class use * Code Style | Class Templates options (Tools | IDE Options). */ package com.intellij.refactoring.introduceParameter; import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; 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.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.SuggestedNameInfo; import com.intellij.psi.codeStyle.VariableKind; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.IntroduceHandlerBase; import com.intellij.refactoring.IntroduceParameterRefactoring; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; import com.intellij.refactoring.introduceField.ElementToWorkOn; import com.intellij.refactoring.ui.MethodCellRenderer; import com.intellij.refactoring.ui.NameSuggestionsGenerator; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.RefactoringUtil; import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBList; import com.intellij.usageView.UsageInfo; import com.intellij.util.ArrayUtil; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class IntroduceParameterHandler extends IntroduceHandlerBase { private static final Logger LOG = Logger .getInstance("#com.intellij.refactoring.introduceParameter.IntroduceParameterHandler"); static final String REFACTORING_NAME = RefactoringBundle.message("introduce.parameter.title"); private JBPopup myEnclosingMethodsPopup; private InplaceIntroduceParameterPopup myInplaceIntroduceParameterPopup; public void invoke(@NotNull final Project project, final Editor editor, PsiFile file, DataContext dataContext) { PsiDocumentManager.getInstance(project).commitAllDocuments(); editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); ElementToWorkOn.processElementToWorkOn(editor, file, REFACTORING_NAME, HelpID.INTRODUCE_PARAMETER, project, new ElementToWorkOn.ElementsProcessor<ElementToWorkOn>() { @Override public boolean accept(ElementToWorkOn el) { return true; } @Override public void pass(final ElementToWorkOn elementToWorkOn) { if (elementToWorkOn == null) return; final PsiExpression expr = elementToWorkOn.getExpression(); final PsiLocalVariable localVar = elementToWorkOn.getLocalVariable(); final boolean isInvokedOnDeclaration = elementToWorkOn.isInvokedOnDeclaration(); invoke(editor, project, expr, localVar, isInvokedOnDeclaration); } }); } protected boolean invokeImpl(Project project, PsiExpression tempExpr, Editor editor) { return invoke(editor, project, tempExpr, null, false); } protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) { return invoke(editor, project, null, localVariable, true); } private boolean invoke(final Editor editor, final Project project, final PsiExpression expr, PsiLocalVariable localVar, boolean invokedOnDeclaration) { LOG.assertTrue(!PsiDocumentManager.getInstance(project).hasUncommitedDocuments()); PsiMethod method; if (expr != null) { method = Util.getContainingMethod(expr); } else { method = Util.getContainingMethod(localVar); } if (LOG.isDebugEnabled()) { LOG.debug("expression:" + expr); } if (expr == null && localVar == null) { String message = RefactoringBundle.getCannotRefactorMessage( RefactoringBundle.message("selected.block.should.represent.an.expression")); showErrorMessage(project, message, editor); return false; } if (localVar != null) { final PsiElement parent = localVar.getParent(); if (!(parent instanceof PsiDeclarationStatement)) { String message = RefactoringBundle.getCannotRefactorMessage( RefactoringBundle.message("error.wrong.caret.position.local.or.expression.name")); showErrorMessage(project, message, editor); return false; } } if (method == null) { String message = RefactoringBundle.getCannotRefactorMessage( RefactoringBundle.message("is.not.supported.in.the.current.context", REFACTORING_NAME)); showErrorMessage(project, message, editor); return false; } if (!CommonRefactoringUtil.checkReadOnlyStatus(project, method)) return false; final PsiType typeByExpression = invokedOnDeclaration ? null : RefactoringUtil.getTypeByExpressionWithExpectedType(expr); if (!invokedOnDeclaration && (typeByExpression == null || LambdaUtil.notInferredType(typeByExpression))) { String message = RefactoringBundle.getCannotRefactorMessage( RefactoringBundle.message("type.of.the.selected.expression.cannot.be.determined")); showErrorMessage(project, message, editor); return false; } if (!invokedOnDeclaration && PsiType.VOID.equals(typeByExpression)) { String message = RefactoringBundle .getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type")); showErrorMessage(project, message, editor); return false; } final List<PsiMethod> validEnclosingMethods = getEnclosingMethods(method); if (validEnclosingMethods.isEmpty()) { return false; } final Introducer introducer = new Introducer(project, expr, localVar, editor); final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode(); if (validEnclosingMethods.size() == 1 || unitTestMode) { final PsiMethod methodToIntroduceParameterTo = validEnclosingMethods.get(0); if (methodToIntroduceParameterTo.findDeepestSuperMethod() == null || unitTestMode) { introducer.introduceParameter(methodToIntroduceParameterTo, methodToIntroduceParameterTo); return true; } } chooseMethodToIntroduceParameter(editor, validEnclosingMethods, introducer); return true; } private void chooseMethodToIntroduceParameter(final Editor editor, final List<PsiMethod> validEnclosingMethods, final Introducer introducer) { final AbstractInplaceIntroducer inplaceIntroducer = AbstractInplaceIntroducer.getActiveIntroducer(editor); if (inplaceIntroducer instanceof InplaceIntroduceParameterPopup) { final InplaceIntroduceParameterPopup introduceParameterPopup = (InplaceIntroduceParameterPopup) inplaceIntroducer; introducer.introduceParameter(introduceParameterPopup.getMethodToIntroduceParameter(), introduceParameterPopup.getMethodToSearchFor()); return; } final JPanel panel = new JPanel(new BorderLayout()); final JCheckBox superMethod = new JCheckBox("Refactor super method", true); superMethod.setMnemonic('U'); panel.add(superMethod, BorderLayout.SOUTH); final JBList list = new JBList(validEnclosingMethods.toArray()); list.setVisibleRowCount(5); list.setCellRenderer(new MethodCellRenderer()); list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setSelectedIndex(0); final List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); final TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme() .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(final ListSelectionEvent e) { final PsiMethod selectedMethod = (PsiMethod) list.getSelectedValue(); if (selectedMethod == null) return; dropHighlighters(highlighters); updateView(selectedMethod, editor, attributes, highlighters, superMethod); } }); updateView(validEnclosingMethods.get(0), editor, attributes, highlighters, superMethod); final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(list); scrollPane.setBorder(null); panel.add(scrollPane, BorderLayout.CENTER); final List<Pair<ActionListener, KeyStroke>> keyboardActions = Collections .singletonList(Pair.<ActionListener, KeyStroke>create(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final PsiMethod methodToSearchIn = (PsiMethod) list.getSelectedValue(); if (myEnclosingMethodsPopup != null && myEnclosingMethodsPopup.isVisible()) { myEnclosingMethodsPopup.cancel(); } final PsiMethod methodToSearchFor = superMethod.isEnabled() && superMethod.isSelected() ? methodToSearchIn.findDeepestSuperMethod() : methodToSearchIn; Runnable runnable = new Runnable() { public void run() { introducer.introduceParameter(methodToSearchIn, methodToSearchFor); } }; IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable); } }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0))); myEnclosingMethodsPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, list) .setTitle("Introduce parameter to method").setMovable(false).setResizable(false) .setRequestFocus(true).setKeyboardActions(keyboardActions).addListener(new JBPopupAdapter() { @Override public void onClosed(LightweightWindowEvent event) { dropHighlighters(highlighters); } }).createPopup(); myEnclosingMethodsPopup.showInBestPositionFor(editor); } private static void updateView(PsiMethod selectedMethod, Editor editor, TextAttributes attributes, List<RangeHighlighter> highlighters, JCheckBox superMethod) { final MarkupModel markupModel = editor.getMarkupModel(); final PsiIdentifier nameIdentifier = selectedMethod.getNameIdentifier(); if (nameIdentifier != null) { final TextRange textRange = nameIdentifier.getTextRange(); final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter(textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, attributes, HighlighterTargetArea.EXACT_RANGE); highlighters.add(rangeHighlighter); } superMethod.setEnabled(selectedMethod.findDeepestSuperMethod() != null); } private static void dropHighlighters(List<RangeHighlighter> highlighters) { for (RangeHighlighter highlighter : highlighters) { highlighter.dispose(); } highlighters.clear(); } protected static NameSuggestionsGenerator createNameSuggestionGenerator(final PsiExpression expr, final String propName, final Project project, final String enteredName) { return new NameSuggestionsGenerator() { public SuggestedNameInfo getSuggestedNameInfo(PsiType type) { final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); SuggestedNameInfo info = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, propName, expr != null && expr.isValid() ? expr : null, type); if (expr != null && expr.isValid()) { info = codeStyleManager.suggestUniqueVariableName(info, expr, true); } final String[] strings = AbstractJavaInplaceIntroducer.appendUnresolvedExprName( JavaCompletionUtil.completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, info), expr); return new SuggestedNameInfo.Delegate( enteredName != null ? ArrayUtil.mergeArrays(new String[] { enteredName }, strings) : strings, info); } }; } private static void showErrorMessage(Project project, String message, Editor editor) { CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INTRODUCE_PARAMETER); } public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { // Never called /* do nothing */ } public static List<PsiMethod> getEnclosingMethods(PsiMethod nearest) { List<PsiMethod> enclosingMethods = new ArrayList<PsiMethod>(); enclosingMethods.add(nearest); PsiMethod method = nearest; while (true) { method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); if (method == null) break; enclosingMethods.add(method); } if (enclosingMethods.size() > 1) { List<PsiMethod> methodsNotImplementingLibraryInterfaces = new ArrayList<PsiMethod>(); for (PsiMethod enclosing : enclosingMethods) { PsiMethod[] superMethods = enclosing.findDeepestSuperMethods(); boolean libraryInterfaceMethod = false; for (PsiMethod superMethod : superMethods) { libraryInterfaceMethod |= isLibraryInterfaceMethod(superMethod); } if (!libraryInterfaceMethod) { methodsNotImplementingLibraryInterfaces.add(enclosing); } } if (methodsNotImplementingLibraryInterfaces.size() > 0) { return methodsNotImplementingLibraryInterfaces; } } return enclosingMethods; } @Nullable public static PsiMethod chooseEnclosingMethod(@NotNull PsiMethod method) { final List<PsiMethod> validEnclosingMethods = getEnclosingMethods(method); if (validEnclosingMethods.size() > 1 && !ApplicationManager.getApplication().isUnitTestMode()) { final EnclosingMethodSelectionDialog dialog = new EnclosingMethodSelectionDialog(method.getProject(), validEnclosingMethods); dialog.show(); if (!dialog.isOK()) return null; method = dialog.getSelectedMethod(); } else if (validEnclosingMethods.size() == 1) { method = validEnclosingMethods.get(0); } return method; } private static boolean isLibraryInterfaceMethod(final PsiMethod method) { return method.hasModifierProperty(PsiModifier.ABSTRACT) && !method.getManager().isInProject(method); } private class Introducer { private final Project myProject; private PsiExpression myExpr; private PsiLocalVariable myLocalVar; private final Editor myEditor; public Introducer(Project project, PsiExpression expr, PsiLocalVariable localVar, Editor editor) { myProject = project; myExpr = expr; myLocalVar = localVar; myEditor = editor; } public void introduceParameter(PsiMethod method, PsiMethod methodToSearchFor) { if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, methodToSearchFor)) return; PsiExpression[] occurences; if (myExpr != null) { occurences = new ExpressionOccurrenceManager(myExpr, method, null).findExpressionOccurrences(); } else { // local variable occurences = CodeInsightUtil.findReferenceExpressions(method, myLocalVar); } String enteredName = null; boolean replaceAllOccurrences = false; boolean delegate = false; PsiType initializerType = IntroduceParameterProcessor.getInitializerType(null, myExpr, myLocalVar); final AbstractInplaceIntroducer activeIntroducer = AbstractInplaceIntroducer .getActiveIntroducer(myEditor); if (activeIntroducer != null) { activeIntroducer.stopIntroduce(myEditor); myExpr = (PsiExpression) activeIntroducer.getExpr(); myLocalVar = (PsiLocalVariable) activeIntroducer.getLocalVariable(); occurences = (PsiExpression[]) activeIntroducer.getOccurrences(); enteredName = activeIntroducer.getInputName(); replaceAllOccurrences = activeIntroducer.isReplaceAllOccurrences(); delegate = ((InplaceIntroduceParameterPopup) activeIntroducer).isGenerateDelegate(); initializerType = ((AbstractJavaInplaceIntroducer) activeIntroducer).getType(); } boolean mustBeFinal = false; for (PsiExpression occurrence : occurences) { if (PsiTreeUtil.getParentOfType(occurrence, PsiClass.class, PsiMethod.class) != method) { mustBeFinal = true; break; } } List<UsageInfo> localVars = new ArrayList<UsageInfo>(); List<UsageInfo> classMemberRefs = new ArrayList<UsageInfo>(); List<UsageInfo> params = new ArrayList<UsageInfo>(); if (myExpr != null) { Util.analyzeExpression(myExpr, localVars, classMemberRefs, params); } final String propName = myLocalVar != null ? JavaCodeStyleManager.getInstance(myProject) .variableNameToPropertyName(myLocalVar.getName(), VariableKind.LOCAL_VARIABLE) : null; boolean isInplaceAvailableOnDataContext = myEditor != null && myEditor.getSettings().isVariableInplaceRenameEnabled(); if (myExpr != null) { isInplaceAvailableOnDataContext &= myExpr.isPhysical(); } if (isInplaceAvailableOnDataContext && activeIntroducer == null) { myInplaceIntroduceParameterPopup = new InplaceIntroduceParameterPopup(myProject, myEditor, classMemberRefs, createTypeSelectorManager(occurences, initializerType), myExpr, myLocalVar, method, methodToSearchFor, occurences, getParamsToRemove(method, occurences), mustBeFinal); if (myInplaceIntroduceParameterPopup.startInplaceIntroduceTemplate()) { return; } } if (ApplicationManager.getApplication().isUnitTestMode()) { @NonNls String parameterName = "anObject"; boolean replaceAllOccurences = true; boolean isDeleteLocalVariable = true; PsiExpression initializer = myLocalVar != null && myExpr == null ? myLocalVar.getInitializer() : myExpr; new IntroduceParameterProcessor(myProject, method, methodToSearchFor, initializer, myExpr, myLocalVar, isDeleteLocalVariable, parameterName, replaceAllOccurences, IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE, mustBeFinal, false, null, getParamsToRemove(method, occurences)).run(); } else { if (myEditor != null) { RefactoringUtil.highlightAllOccurrences(myProject, occurences, myEditor); } final IntroduceParameterDialog dialog = new IntroduceParameterDialog(myProject, classMemberRefs, occurences, myLocalVar, myExpr, createNameSuggestionGenerator(myExpr, propName, myProject, enteredName), createTypeSelectorManager(occurences, initializerType), methodToSearchFor, method, getParamsToRemove(method, occurences), mustBeFinal); dialog.setReplaceAllOccurrences(replaceAllOccurrences); dialog.setGenerateDelegate(delegate); dialog.show(); if (myEditor != null) { myEditor.getSelectionModel().removeSelection(); } } } private TypeSelectorManagerImpl createTypeSelectorManager(PsiExpression[] occurences, PsiType initializerType) { return myExpr != null ? new TypeSelectorManagerImpl(myProject, initializerType, myExpr, occurences) : new TypeSelectorManagerImpl(myProject, initializerType, occurences); } private TIntArrayList getParamsToRemove(PsiMethod method, PsiExpression[] occurences) { PsiExpression expressionToRemoveParamFrom = myExpr; if (myExpr == null) { expressionToRemoveParamFrom = myLocalVar.getInitializer(); } return expressionToRemoveParamFrom == null ? new TIntArrayList() : Util.findParametersToRemove(method, expressionToRemoveParamFrom, occurences); } } @Override public AbstractInplaceIntroducer getInplaceIntroducer() { return myInplaceIntroduceParameterPopup; } }