com.intellij.refactoring.move.moveInner.MoveInnerProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.refactoring.move.moveInner.MoveInnerProcessor.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.
 */
package com.intellij.refactoring.move.moveInner;

import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.util.ConflictsUtil;
import com.intellij.refactoring.util.NonCodeUsageInfo;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.consulo.psi.PsiPackage;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * created at Sep 24, 2001
 * @author Jeka
 */
public class MoveInnerProcessor extends BaseRefactoringProcessor {
    private static final Logger LOG = Logger
            .getInstance("#com.intellij.refactoring.move.moveInner.MoveInnerProcessor");

    private MoveCallback myMoveCallback;

    private PsiClass myInnerClass;
    private PsiClass myOuterClass;
    private PsiElement myTargetContainer;
    private String myParameterNameOuterClass;
    private String myFieldNameOuterClass;
    private String myDescriptiveName = "";
    private String myNewClassName;
    private boolean mySearchInComments;
    private boolean mySearchInNonJavaFiles;
    private NonCodeUsageInfo[] myNonCodeUsages;

    public MoveInnerProcessor(Project project, MoveCallback moveCallback) {
        super(project);
        myMoveCallback = moveCallback;
    }

    public MoveInnerProcessor(Project project, PsiClass innerClass, String name, boolean passOuterClass,
            String parameterName, final PsiElement targetContainer) {
        super(project);
        setup(innerClass, name, passOuterClass, parameterName, true, true, targetContainer);
    }

    protected String getCommandName() {
        return RefactoringBundle.message("move.inner.class.command", myDescriptiveName);
    }

    @NotNull
    protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
        return new MoveInnerViewDescriptor(myInnerClass);
    }

    @NotNull
    protected UsageInfo[] findUsages() {
        LOG.assertTrue(myTargetContainer != null);

        Collection<PsiReference> innerClassRefs = ReferencesSearch.search(myInnerClass).findAll();
        ArrayList<UsageInfo> usageInfos = new ArrayList<UsageInfo>(innerClassRefs.size());
        for (PsiReference innerClassRef : innerClassRefs) {
            PsiElement ref = innerClassRef.getElement();
            if (!PsiTreeUtil.isAncestor(myInnerClass, ref, true)) { // do not show self-references
                usageInfos.add(new UsageInfo(ref));
            }
        }

        final String newQName;
        if (myTargetContainer instanceof PsiDirectory) {
            final PsiDirectory targetDirectory = (PsiDirectory) myTargetContainer;
            final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory);
            LOG.assertTrue(aPackage != null);
            newQName = aPackage.getQualifiedName() + "." + myNewClassName;
        } else if (myTargetContainer instanceof PsiClass) {
            final String qName = ((PsiClass) myTargetContainer).getQualifiedName();
            if (qName != null) {
                newQName = qName + "." + myNewClassName;
            } else {
                newQName = myNewClassName;
            }
        } else {
            newQName = myNewClassName;
        }
        MoveClassesOrPackagesUtil.findNonCodeUsages(mySearchInComments, mySearchInNonJavaFiles, myInnerClass,
                newQName, usageInfos);
        return usageInfos.toArray(new UsageInfo[usageInfos.size()]);
    }

    protected void refreshElements(PsiElement[] elements) {
        boolean condition = elements.length == 1 && elements[0] instanceof PsiClass;
        LOG.assertTrue(condition);
        myInnerClass = (PsiClass) elements[0];
    }

    public boolean isSearchInComments() {
        return mySearchInComments;
    }

    public void setSearchInComments(boolean searchInComments) {
        mySearchInComments = searchInComments;
    }

    public boolean isSearchInNonJavaFiles() {
        return mySearchInNonJavaFiles;
    }

    public void setSearchInNonJavaFiles(boolean searchInNonJavaFiles) {
        mySearchInNonJavaFiles = searchInNonJavaFiles;
    }

    protected void performRefactoring(final UsageInfo[] usages) {
        final PsiManager manager = PsiManager.getInstance(myProject);
        final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();

        final RefactoringElementListener elementListener = getTransaction().getElementListener(myInnerClass);
        try {
            PsiField field = null;
            if (myParameterNameOuterClass != null) {
                // pass outer as a parameter
                field = factory.createField(myFieldNameOuterClass, factory.createType(myOuterClass));
                field = addOuterField(field);
                myInnerClass = field.getContainingClass();
                addFieldInitializationToConstructors(myInnerClass, field, myParameterNameOuterClass);
            }

            ChangeContextUtil.encodeContextInfo(myInnerClass, false);

            myInnerClass = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myInnerClass);

            final MoveInnerOptions moveInnerOptions = new MoveInnerOptions(myInnerClass, myOuterClass,
                    myTargetContainer, myNewClassName);
            final MoveInnerHandler handler = MoveInnerHandler.EP_NAME.forLanguage(myInnerClass.getLanguage());
            final PsiClass newClass;
            try {
                newClass = handler.copyClass(moveInnerOptions);
            } catch (IncorrectOperationException e) {
                RefactoringUIUtil.processIncorrectOperation(myProject, e);
                return;
            }

            // replace references in a new class to old inner class with references to itself
            for (PsiReference ref : ReferencesSearch.search(myInnerClass, new LocalSearchScope(newClass), true)) {
                PsiElement element = ref.getElement();
                if (element.getParent() instanceof PsiJavaCodeReferenceElement) {
                    PsiJavaCodeReferenceElement parentRef = (PsiJavaCodeReferenceElement) element.getParent();
                    PsiElement parentRefElement = parentRef.resolve();
                    if (parentRefElement instanceof PsiClass) { // reference to inner class inside our inner
                        parentRef.getQualifier().delete();
                        continue;
                    }
                }
                ref.bindToElement(newClass);
            }

            List<PsiReference> referencesToRebind = new ArrayList<PsiReference>();
            for (UsageInfo usage : usages) {
                if (usage.isNonCodeUsage)
                    continue;
                PsiElement refElement = usage.getElement();
                PsiReference[] references = refElement.getReferences();
                for (PsiReference reference : references) {
                    if (reference.isReferenceTo(myInnerClass)) {
                        referencesToRebind.add(reference);
                    }
                }
            }

            myInnerClass.delete();

            // correct references in usages
            for (UsageInfo usage : usages) {
                if (usage.isNonCodeUsage)
                    continue;
                PsiElement refElement = usage.getElement();
                if (myParameterNameOuterClass != null) { // should pass outer as parameter
                    PsiElement refParent = refElement.getParent();
                    if (refParent instanceof PsiNewExpression || refParent instanceof PsiAnonymousClass) {
                        PsiNewExpression newExpr = refParent instanceof PsiNewExpression
                                ? (PsiNewExpression) refParent
                                : (PsiNewExpression) refParent.getParent();

                        PsiExpressionList argList = newExpr.getArgumentList();

                        if (argList != null) { // can happen in incomplete code
                            if (newExpr.getQualifier() == null) {
                                PsiThisExpression thisExpr;
                                PsiClass parentClass = RefactoringChangeUtil.getThisClass(newExpr);
                                if (myOuterClass.equals(parentClass)) {
                                    thisExpr = RefactoringChangeUtil.createThisExpression(manager, null);
                                } else {
                                    thisExpr = RefactoringChangeUtil.createThisExpression(manager, myOuterClass);
                                }
                                argList.addAfter(thisExpr, null);
                            } else {
                                argList.addAfter(newExpr.getQualifier(), null);
                                newExpr.getQualifier().delete();
                            }
                        }
                    }
                }
            }

            for (PsiReference reference : referencesToRebind) {
                reference.bindToElement(newClass);
            }

            if (field != null) {
                final PsiExpression paramAccessExpression = factory
                        .createExpressionFromText(myParameterNameOuterClass, null);
                for (final PsiMethod constructor : newClass.getConstructors()) {
                    final PsiStatement[] statements = constructor.getBody().getStatements();
                    if (statements.length > 0) {
                        if (statements[0] instanceof PsiExpressionStatement) {
                            PsiExpression expression = ((PsiExpressionStatement) statements[0]).getExpression();
                            if (expression instanceof PsiMethodCallExpression) {
                                @NonNls
                                String text = ((PsiMethodCallExpression) expression).getMethodExpression()
                                        .getText();
                                if ("this".equals(text) || "super".equals(text)) {
                                    ChangeContextUtil.decodeContextInfo(expression, myOuterClass,
                                            paramAccessExpression);
                                }
                            }
                        }
                    }
                }

                PsiExpression accessExpression = factory.createExpressionFromText(myFieldNameOuterClass, null);
                ChangeContextUtil.decodeContextInfo(newClass, myOuterClass, accessExpression);
            } else {
                ChangeContextUtil.decodeContextInfo(newClass, null, null);
            }

            PsiFile targetFile = newClass.getContainingFile();
            OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, targetFile.getVirtualFile(),
                    newClass.getTextOffset());
            FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true);

            if (myMoveCallback != null) {
                myMoveCallback.refactoringCompleted();
            }
            elementListener.elementMoved(newClass);

            List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<NonCodeUsageInfo>();
            for (UsageInfo usage : usages) {
                if (usage instanceof NonCodeUsageInfo) {
                    nonCodeUsages.add((NonCodeUsageInfo) usage);
                }
            }
            myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]);
        } catch (IncorrectOperationException e) {
            LOG.error(e);
        }
    }

    private PsiField addOuterField(PsiField field) {
        final PsiMember[] members = PsiTreeUtil.getChildrenOfType(myInnerClass, PsiMember.class);
        if (members != null) {
            for (PsiMember member : members) {
                if (!member.hasModifierProperty(PsiModifier.STATIC)) {
                    return (PsiField) myInnerClass.addBefore(field, member);
                }
            }
        }

        return (PsiField) myInnerClass.add(field);
    }

    protected void performPsiSpoilingRefactoring() {
        if (myNonCodeUsages != null) {
            RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
        }
    }

    protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
        final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
        final HashMap<PsiElement, HashSet<PsiElement>> reported = new HashMap<PsiElement, HashSet<PsiElement>>();
        class Visitor extends JavaRecursiveElementWalkingVisitor {

            @Override
            public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
                PsiElement resolved = reference.resolve();
                if (resolved instanceof PsiMember && PsiTreeUtil.isAncestor(myInnerClass, resolved, true)
                        && becomesInaccessible((PsiMember) resolved)) {
                    registerConflict(reference, resolved, reported, conflicts);
                }
            }

            @Override
            public void visitClass(PsiClass aClass) {
                if (aClass == myInnerClass)
                    return;
                super.visitClass(aClass);
            }
        }

        //    if (myInnerClass.hasModifierProperty(PsiModifier.)) {
        myOuterClass.accept(new Visitor());
        myInnerClass.accept(new JavaRecursiveElementWalkingVisitor() {
            @Override
            public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
                super.visitReferenceElement(reference);
                final PsiElement resolve = reference.resolve();
                if (resolve instanceof PsiMember) {
                    if (PsiTreeUtil.isAncestor(myOuterClass, resolve, true)
                            && !PsiTreeUtil.isAncestor(myInnerClass, resolve, false)) {
                        if (becomesInaccessible((PsiMember) resolve)) {
                            registerConflict(reference, resolve, reported, conflicts);
                        }
                    }
                }
            }
        });

        return showConflicts(conflicts, refUsages.get());
    }

    private static void registerConflict(PsiJavaCodeReferenceElement reference, PsiElement resolved,
            HashMap<PsiElement, HashSet<PsiElement>> reported, MultiMap<PsiElement, String> conflicts) {
        final PsiElement container = ConflictsUtil.getContainer(reference);
        HashSet<PsiElement> containerSet = reported.get(container);
        if (containerSet == null) {
            containerSet = new HashSet<PsiElement>();
            reported.put(container, containerSet);
        }
        if (!containerSet.contains(resolved)) {
            containerSet.add(resolved);
            String placesDescription;
            if (containerSet.size() == 1) {
                placesDescription = RefactoringUIUtil.getDescription(resolved, true);
            } else {
                placesDescription = "<ol><li>" + StringUtil.join(containerSet, new Function<PsiElement, String>() {
                    @Override
                    public String fun(PsiElement element) {
                        return RefactoringUIUtil.getDescription(element, true);
                    }
                }, "</li><li>") + "</li></ol>";
            }
            String message = RefactoringBundle.message("0.will.become.inaccessible.from.1", placesDescription,
                    RefactoringUIUtil.getDescription(container, true));
            conflicts.put(container, Collections.singletonList(message));
        }
    }

    private boolean becomesInaccessible(PsiMember element) {
        final String visibilityModifier = VisibilityUtil.getVisibilityModifier(element.getModifierList());
        if (PsiModifier.PRIVATE.equals(visibilityModifier))
            return true;
        if (PsiModifier.PUBLIC.equals(visibilityModifier))
            return false;
        final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(myProject);
        if (myTargetContainer instanceof PsiDirectory) {
            final PsiJavaPackage aPackage = JavaDirectoryService.getInstance()
                    .getPackage((PsiDirectory) myTargetContainer);
            assert aPackage != null : myTargetContainer;
            return !psiFacade.isInPackage(myOuterClass, aPackage);
        }
        // target container is a class
        PsiFile targetFile = myTargetContainer.getContainingFile();
        if (targetFile != null) {
            final PsiDirectory containingDirectory = targetFile.getContainingDirectory();
            if (containingDirectory != null) {
                final PsiJavaPackage targetPackage = JavaDirectoryService.getInstance()
                        .getPackage(containingDirectory);
                assert targetPackage != null : myTargetContainer;
                return psiFacade.isInPackage(myOuterClass, targetPackage);
            }
        }
        return false;
    }

    public void setup(final PsiClass innerClass, final String className, final boolean passOuterClass,
            final String parameterName, boolean searchInComments, boolean searchInNonJava,
            @NotNull final PsiElement targetContainer) {
        myNewClassName = className;
        myInnerClass = innerClass;
        myDescriptiveName = DescriptiveNameUtil.getDescriptiveName(myInnerClass);
        myOuterClass = myInnerClass.getContainingClass();
        myTargetContainer = targetContainer;
        JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myProject);
        myParameterNameOuterClass = passOuterClass ? parameterName : null;
        if (myParameterNameOuterClass != null) {
            myFieldNameOuterClass = codeStyleManager.variableNameToPropertyName(myParameterNameOuterClass,
                    VariableKind.PARAMETER);
            myFieldNameOuterClass = codeStyleManager.propertyNameToVariableName(myFieldNameOuterClass,
                    VariableKind.FIELD);
        }
        mySearchInComments = searchInComments;
        mySearchInNonJavaFiles = searchInNonJava;
    }

    private void addFieldInitializationToConstructors(PsiClass aClass, PsiField field, String parameterName)
            throws IncorrectOperationException {

        PsiMethod[] constructors = aClass.getConstructors();
        PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
        if (constructors.length > 0) {
            for (PsiMethod constructor : constructors) {
                if (parameterName != null) {
                    PsiParameterList parameterList = constructor.getParameterList();
                    PsiParameter parameter = factory.createParameter(parameterName, field.getType());
                    parameterList.addAfter(parameter, null);
                }
                PsiCodeBlock body = constructor.getBody();
                if (body == null)
                    continue;
                PsiStatement[] statements = body.getStatements();
                if (statements.length > 0) {
                    PsiStatement first = statements[0];
                    if (first instanceof PsiExpressionStatement) {
                        PsiExpression expression = ((PsiExpressionStatement) first).getExpression();
                        if (expression instanceof PsiMethodCallExpression) {
                            @NonNls
                            String text = ((PsiMethodCallExpression) expression).getMethodExpression().getText();
                            if ("this".equals(text)) {
                                continue;
                            }
                        }
                    }
                }
                createAssignmentStatement(constructor, field.getName(), parameterName);
            }
        } else {
            PsiMethod constructor = factory.createConstructor();
            if (parameterName != null) {
                PsiParameterList parameterList = constructor.getParameterList();
                PsiParameter parameter = factory.createParameter(parameterName, field.getType());
                parameterList.add(parameter);
            }
            createAssignmentStatement(constructor, field.getName(), parameterName);
            aClass.add(constructor);
        }
    }

    private PsiStatement createAssignmentStatement(PsiMethod constructor, String fieldName, String parameterName)
            throws IncorrectOperationException {

        PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
        @NonNls
        String pattern = fieldName + "=a;";
        if (fieldName.equals(parameterName)) {
            pattern = "this." + pattern;
        }

        PsiExpressionStatement statement = (PsiExpressionStatement) factory.createStatementFromText(pattern, null);
        statement = (PsiExpressionStatement) CodeStyleManager.getInstance(myProject).reformat(statement);

        PsiCodeBlock body = constructor.getBody();
        assert body != null : constructor;
        statement = (PsiExpressionStatement) body.addAfter(statement, getAnchorElement(body));

        PsiAssignmentExpression assignment = (PsiAssignmentExpression) statement.getExpression();
        PsiReferenceExpression rExpr = (PsiReferenceExpression) assignment.getRExpression();
        assert rExpr != null : assignment;
        PsiIdentifier identifier = (PsiIdentifier) rExpr.getReferenceNameElement();
        assert identifier != null : assignment;
        identifier.replace(factory.createIdentifier(parameterName));
        return statement;
    }

    @Nullable
    private static PsiElement getAnchorElement(PsiCodeBlock body) {
        PsiStatement[] statements = body.getStatements();
        if (statements.length > 0) {
            PsiStatement first = statements[0];
            if (first instanceof PsiExpressionStatement) {

                PsiExpression expression = ((PsiExpressionStatement) first).getExpression();
                if (expression instanceof PsiMethodCallExpression) {
                    PsiReferenceExpression methodCall = ((PsiMethodCallExpression) expression)
                            .getMethodExpression();
                    @NonNls
                    String text = methodCall.getText();
                    if ("super".equals(text)) {
                        return first;
                    }
                }
            }
        }
        return null;
    }

    public PsiClass getInnerClass() {
        return myInnerClass;
    }

    public String getNewClassName() {
        return myNewClassName;
    }

    public boolean shouldPassParameter() {
        return myParameterNameOuterClass != null;
    }

    public String getParameterName() {
        return myParameterNameOuterClass;
    }
}