com.intellij.refactoring.introduceParameter.IntroduceParameterHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.refactoring.introduceParameter.IntroduceParameterHandler.java

Source

/*
 * 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;
    }
}