com.intellij.refactoring.extractSuperclass.ExtractSuperClassMultiUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.refactoring.extractSuperclass.ExtractSuperClassMultiUtil.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.
 */

/*
 * Extended by Julien Cohen (Ascola team, Univ. Nantes), Feb/March 2012.
 * Copyright 2012 Universit de Nantes for those contributions.            
 */

package com.intellij.refactoring.extractSuperclass;

import com.intellij.codeInsight.generation.OverrideImplementUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.genUtils.GenAnalysisUtils;
import com.intellij.refactoring.memberPullUp.PullUpGenProcessor;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.DocCommentPolicy;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class ExtractSuperClassMultiUtil {
    private static final Logger LOG = Logger
            .getInstance("com.intellij.refactoring.extractSuperclassMulti.ExtractSuperClassMultiUtil");

    private ExtractSuperClassMultiUtil() {
    }

    // Modified (Julien)
    public static PsiClass extractSuperClassMulti(final Project project, final PsiDirectory targetDirectory,
            final String superclassName, final PsiClass subclass, final MemberInfo[] selectedMemberInfos,
            final DocCommentPolicy javaDocPolicy, final boolean useGenericUnification)
            throws IncorrectOperationException {
        final Collection<PsiClass> sisterClasses = GenAnalysisUtils.findSisterClassesInDirectory(subclass); // rem : in extract super-class, we are only interested in classes at the same level. For instance, if we have A->Object, B->Object, C->B->Object, we are not interested in C (unlike in pull-up abstract).
        final String packageName = ((PsiJavaFile) subclass.getContainingFile()).getPackageName();

        final Collection<PsiClass> selectedSisterClasses = filterSisterClasses(Arrays.asList(selectedMemberInfos),
                useGenericUnification, sisterClasses);

        if (selectedSisterClasses.isEmpty()) {
            throw new IncorrectOperationException(
                    "Internal error: no convenient class found (in extractSuperClassMulti).");
        }

        return extractSuperClass(project, targetDirectory, superclassName, selectedSisterClasses,
                selectedMemberInfos, javaDocPolicy, useGenericUnification);
    }

    // Filter sister classes which have the selected members
    public static Collection<PsiClass> filterSisterClasses(Iterable<MemberInfo> selectedMemberInfos,
            boolean useGenericUnification, Collection<PsiClass> sisterClasses) {
        Collection<PsiClass> selectedClasses = new Vector();

        if (!useGenericUnification) {
            for (PsiClass c : sisterClasses) {
                if (GenAnalysisUtils.hasMembers(c, selectedMemberInfos))
                    selectedClasses.add(c);
            }
        }

        else {
            for (PsiClass c : sisterClasses) {
                try {
                    if (GenAnalysisUtils.hasCompatibleMembers(c, selectedMemberInfos))
                        selectedClasses.add(c);
                } catch (GenAnalysisUtils.AmbiguousOverloading ambiguousOverloading) {
                    throw new IncorrectOperationException(ambiguousOverloading.toString());
                }
            }
        }

        return selectedClasses;
    }

    // Modified (Julien)
    public static PsiClass extractSuperClass(final Project project, final PsiDirectory targetDirectory,
            final String superclassName, final Collection<PsiClass> subclasses,
            final MemberInfo[] selectedMemberInfos, final DocCommentPolicy javaDocPolicy,
            final boolean useGenericUnification) throws IncorrectOperationException {

        assert (!subclasses.isEmpty());
        PsiClass aSubClass = subclasses.iterator().next();

        // 1 : Create an empty class
        PsiClass myfreshsuperclass = JavaDirectoryService.getInstance().createClass(targetDirectory,
                superclassName);
        final PsiModifierList superClassModifierList = myfreshsuperclass.getModifierList();
        assert superClassModifierList != null;
        superClassModifierList.setModifierProperty(PsiModifier.FINAL, false);
        superClassModifierList.setModifierProperty(PsiModifier.ABSTRACT, true);

        // 2 : make the correct 'extends' for the new class

        final PsiReferenceList subClassExtends = aSubClass.getExtendsList(); // TODO : check that other classes do not need to be considered as well
        assert subClassExtends != null : subclasses;
        copyPsiReferenceList(subClassExtends, myfreshsuperclass.getExtendsList());

        // 3 : create constructors if neccesary
        /* PsiMethod[] constructors = getCalledBaseConstructors(subclasses);
        if (constructors.length > 0) {
          createConstructorsByPattern(project, superclass, constructors);
        }  */ // TODO : reactivate these lines (and find the test that justifies that)

        // 4 : create new 'extends' links
        for (PsiClass c : subclasses) {
            clearPsiReferenceList(c.getExtendsList());
            PsiJavaCodeReferenceElement ref = createExtendingReference(myfreshsuperclass, c, selectedMemberInfos);
            c.getExtendsList().add(ref);
        }

        // 5 : pull-up selected members (and 'implements')

        /*if (!useGenericUnification) {
            
          PullUpHelper pullUpHelper = new PullUpHelper(aSubClass, myfreshsuperclass, selectedMemberInfos, // TODO: consider other subclasses than [0]  + maybe can use pullUpGenHelper here too?
                                             javaDocPolicy
                                );
          pullUpHelper.moveMembersToBase();
          pullUpHelper.moveFieldInitializations();
        }
        else {  */
        PullUpGenProcessor pullUpHelper = new PullUpGenProcessor(aSubClass, subclasses, myfreshsuperclass,
                selectedMemberInfos, javaDocPolicy);
        try {
            pullUpHelper.moveMembersToBase(); // TODO : make that efficient (unifiers are searched twice: one time for computing the sister classes, and one time for the pull-up)
        } catch (GenAnalysisUtils.AmbiguousOverloading ambiguousOverloading) {
            throw new IncorrectOperationException(ambiguousOverloading.toString());
        } catch (GenAnalysisUtils.MemberNotImplemented notImplemented) {
            throw new IncorrectOperationException(notImplemented.toString());
        }
        pullUpHelper.moveFieldInitializations();

        /* } */

        // 6 : make the superclass abstract if needed
        Collection<MethodSignature> toImplement = OverrideImplementUtil
                .getMethodSignaturesToImplement(myfreshsuperclass);
        if (!toImplement.isEmpty()) {
            superClassModifierList.setModifierProperty(PsiModifier.ABSTRACT, true);
        }

        // finished
        return myfreshsuperclass;
    }

    private static void createConstructorsByPattern(Project project, final PsiClass superclass,
            PsiMethod[] patternConstructors) throws IncorrectOperationException {
        PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
        CodeStyleManager styleManager = CodeStyleManager.getInstance(project);
        for (PsiMethod baseConstructor : patternConstructors) {
            PsiMethod constructor = (PsiMethod) superclass.add(factory.createConstructor());
            PsiParameterList paramList = constructor.getParameterList();
            PsiParameter[] baseParams = baseConstructor.getParameterList().getParameters();
            @NonNls
            StringBuilder superCallText = new StringBuilder();
            superCallText.append("super(");
            final PsiClass baseClass = baseConstructor.getContainingClass();
            LOG.assertTrue(baseClass != null);
            final PsiSubstitutor classSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass,
                    superclass, PsiSubstitutor.EMPTY);
            for (int i = 0; i < baseParams.length; i++) {
                final PsiParameter baseParam = baseParams[i];
                final PsiParameter newParam = (PsiParameter) paramList.add(factory
                        .createParameter(baseParam.getName(), classSubstitutor.substitute(baseParam.getType())));
                if (i > 0) {
                    superCallText.append(",");
                }
                superCallText.append(newParam.getName());
            }
            superCallText.append(");");
            PsiStatement statement = factory.createStatementFromText(superCallText.toString(), null);
            statement = (PsiStatement) styleManager.reformat(statement);
            final PsiCodeBlock body = constructor.getBody();
            assert body != null;
            body.add(statement);
            constructor.getThrowsList().replace(baseConstructor.getThrowsList());
        }
    }

    private static PsiMethod[] getCalledBaseConstructors(final PsiClass subclass) {
        Set<PsiMethod> baseConstructors = new HashSet<PsiMethod>();
        PsiMethod[] constructors = subclass.getConstructors();
        for (PsiMethod constructor : constructors) {
            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) {
                        PsiReferenceExpression calledMethod = ((PsiMethodCallExpression) expression)
                                .getMethodExpression();
                        @NonNls
                        String text = calledMethod.getText();
                        if ("super".equals(text)) {
                            PsiMethod baseConstructor = (PsiMethod) calledMethod.resolve();
                            if (baseConstructor != null) {
                                baseConstructors.add(baseConstructor);
                            }
                        }
                    }
                }
            }
        }
        return baseConstructors.toArray(new PsiMethod[baseConstructors.size()]);
    }

    private static void clearPsiReferenceList(PsiReferenceList refList) throws IncorrectOperationException {
        PsiJavaCodeReferenceElement[] refs = refList.getReferenceElements();
        for (PsiJavaCodeReferenceElement ref : refs) {
            ref.delete();
        }
    }

    private static void copyPsiReferenceList(PsiReferenceList sourceList, PsiReferenceList destinationList)
            throws IncorrectOperationException {
        clearPsiReferenceList(destinationList);
        PsiJavaCodeReferenceElement[] refs = sourceList.getReferenceElements();
        for (PsiJavaCodeReferenceElement ref : refs) {
            destinationList.add(ref);
        }
    }

    public static PsiJavaCodeReferenceElement createExtendingReference(final PsiClass superClass,
            final PsiClass derivedClass, final MemberInfo[] selectedMembers) throws IncorrectOperationException {
        final PsiManager manager = derivedClass.getManager();
        Set<PsiElement> movedElements = new com.intellij.util.containers.HashSet<PsiElement>();
        for (final MemberInfo info : selectedMembers) {
            movedElements.add(info.getMember());
        }
        final PsiTypeParameterList typeParameterList = RefactoringUtil
                .createTypeParameterListWithUsedTypeParameters(null, new Condition<PsiTypeParameter>() {
                    @Override
                    public boolean value(PsiTypeParameter parameter) {
                        return findTypeParameterInDerived(derivedClass, parameter.getName()) != null;
                    }
                }, PsiUtilBase.toPsiElementArray(movedElements));
        final PsiTypeParameterList originalTypeParameterList = superClass.getTypeParameterList();
        assert originalTypeParameterList != null;
        final PsiTypeParameterList newList = typeParameterList != null
                ? (PsiTypeParameterList) originalTypeParameterList.replace(typeParameterList)
                : originalTypeParameterList;
        final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
        Map<PsiTypeParameter, PsiType> substitutionMap = new HashMap<PsiTypeParameter, PsiType>();
        for (final PsiTypeParameter parameter : newList.getTypeParameters()) {
            final PsiTypeParameter parameterInDerived = findTypeParameterInDerived(derivedClass,
                    parameter.getName());
            if (parameterInDerived != null) {
                substitutionMap.put(parameter, factory.createType(parameterInDerived));
            }
        }

        final PsiClassType type = factory.createType(superClass, factory.createSubstitutor(substitutionMap));
        return factory.createReferenceElementByType(type);
    }

    @Nullable
    public static PsiTypeParameter findTypeParameterInDerived(final PsiClass aClass, final String name) {
        for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(aClass)) {
            if (name.equals(typeParameter.getName()))
                return typeParameter;
        }

        return null;
    }

    public static void checkSuperAccessible(PsiDirectory targetDirectory, MultiMap<PsiElement, String> conflicts,
            final PsiClass subclass) {
        final VirtualFile virtualFile = subclass.getContainingFile().getVirtualFile();
        if (virtualFile != null) {
            final boolean inTestSourceContent = ProjectRootManager.getInstance(subclass.getProject()).getFileIndex()
                    .isInTestSourceContent(virtualFile);
            final Module module = ModuleUtil.findModuleForFile(virtualFile, subclass.getProject());
            if (targetDirectory != null && module != null
                    && !GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, inTestSourceContent)
                            .contains(targetDirectory.getVirtualFile())) {
                conflicts.putValue(subclass, "Superclass won't be accessible in subclass");
            }
        }
    }

    public static boolean showConflicts(DialogWrapper dialog, MultiMap<PsiElement, String> conflicts,
            final Project project) {
        if (!conflicts.isEmpty()) {
            ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts);
            conflictsDialog.show();
            final boolean ok = conflictsDialog.isOK();
            if (!ok && conflictsDialog.isShowConflicts())
                dialog.close(DialogWrapper.CANCEL_EXIT_CODE);
            return ok;
        }
        return true;
    }
}