blob: 8812398b4f7563dc0cff4a06c900671141888ec1 [file] [log] [blame]
/*
* Copyright 2000-2013 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.extractSuperclass;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
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.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.listeners.RefactoringEventData;
import com.intellij.refactoring.listeners.RefactoringEventListener;
import com.intellij.refactoring.memberPullUp.PullUpProcessor;
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.Function;
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.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author dsl
*/
public class ExtractSuperClassUtil {
private static final Logger LOG = Logger.getInstance("com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil");
public static final String REFACTORING_EXTRACT_SUPER_ID = "refactoring.extractSuper";
private ExtractSuperClassUtil() {}
public static PsiClass extractSuperClass(final Project project,
final PsiDirectory targetDirectory,
final String superclassName,
final PsiClass subclass,
final MemberInfo[] selectedMemberInfos,
final DocCommentPolicy javaDocPolicy)
throws IncorrectOperationException {
project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
.refactoringStarted(REFACTORING_EXTRACT_SUPER_ID, createBeforeData(subclass, selectedMemberInfos));
final PsiClass superclass = JavaDirectoryService.getInstance().createClass(targetDirectory, superclassName);
try {
final PsiModifierList superClassModifierList = superclass.getModifierList();
assert superClassModifierList != null;
superClassModifierList.setModifierProperty(PsiModifier.FINAL, false);
final PsiReferenceList subClassExtends = subclass.getExtendsList();
if (subClassExtends != null) {
copyPsiReferenceList(subClassExtends, superclass.getExtendsList());
} else if (subclass instanceof PsiAnonymousClass) {
superclass.getExtendsList().add(((PsiAnonymousClass)subclass).getBaseClassReference());
}
// create constructors if neccesary
PsiMethod[] constructors = getCalledBaseConstructors(subclass);
if (constructors.length > 0) {
createConstructorsByPattern(project, superclass, constructors);
}
// clear original class' "extends" list
if (subClassExtends != null) {
clearPsiReferenceList(subclass.getExtendsList());
}
// make original class extend extracted superclass
PsiJavaCodeReferenceElement ref = createExtendingReference(superclass, subclass, selectedMemberInfos);
if (subClassExtends != null) {
subclass.getExtendsList().add(ref);
} else if (subclass instanceof PsiAnonymousClass) {
((PsiAnonymousClass)subclass).getBaseClassReference().replace(ref);
}
PullUpProcessor pullUpHelper = new PullUpProcessor(subclass, superclass, selectedMemberInfos,
javaDocPolicy
);
pullUpHelper.moveMembersToBase();
pullUpHelper.moveFieldInitializations();
Collection<MethodSignature> toImplement = OverrideImplementExploreUtil.getMethodSignaturesToImplement(superclass);
if (!toImplement.isEmpty()) {
superClassModifierList.setModifierProperty(PsiModifier.ABSTRACT, true);
}
return superclass;
}
finally {
project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(REFACTORING_EXTRACT_SUPER_ID, createAfterData(superclass));
}
}
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 Condition<PsiTypeParameter> filter = new Condition<PsiTypeParameter>() {
@Override
public boolean value(PsiTypeParameter parameter) {
return findTypeParameterInDerived(derivedClass, parameter.getName()) == parameter;
}
};
final PsiTypeParameterList typeParameterList = RefactoringUtil.createTypeParameterListWithUsedTypeParameters(null, filter, PsiUtilCore.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()) {
fireConflictsEvent(conflicts, project);
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;
}
private static void fireConflictsEvent(MultiMap<PsiElement, String> conflicts, Project project) {
final RefactoringEventData conflictUsages = new RefactoringEventData();
conflictUsages.putUserData(RefactoringEventData.CONFLICTS_KEY, conflicts.values());
project.getMessageBus()
.syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
.conflictsDetected(REFACTORING_EXTRACT_SUPER_ID, conflictUsages);
}
public static RefactoringEventData createBeforeData(final PsiClass subclassClass, final MemberInfo[] members) {
RefactoringEventData data = new RefactoringEventData();
data.addElement(subclassClass);
data.addMembers(members, new Function<MemberInfo, PsiElement>() {
@Override
public PsiElement fun(MemberInfo info) {
return info.getMember();
}
});
return data;
}
public static RefactoringEventData createAfterData(final PsiClass subClass) {
RefactoringEventData data = new RefactoringEventData();
data.addElement(subClass);
return data;
}
}