/*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: dsl
* Date: 14.06.2002
* Time: 22:35:19
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
/*
* Extended by Julien Cohen (Ascola team, Univ. Nantes), 2012, 2013.
* Copyright 2012, 2013 Université de Nantes for those contributions.
*/
/* Up-to-date w.r.t. Intellij commit of Oct 24, 2013. */
/* Up-to-date w.r.t. Intellij commit of Dec 5, 2013. */
/* Up-to-date w.r.t. Intellij commit of Jan 9, 2014. */
/* Up-to-date w.r.t. Intellij commit of Aug 15, 2014. */
/* Up-to-date w.r.t. Intellij commit of Nov 21, 2014. */
package com.intellij.refactoring.memberPullUp;
import com.intellij.analysis.AnalysisScope;
import com.intellij.lang.Language;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.genUtils.AmbiguousOverloading;
import com.intellij.refactoring.genUtils.MemberNotImplemented;
import com.intellij.refactoring.listeners.JavaRefactoringListenerManager;
import com.intellij.refactoring.listeners.RefactoringEventData;
import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl;
import com.intellij.refactoring.util.DocCommentPolicy;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import com.intellij.refactoring.genUtils.GenBuildUtils;
public class PullUpGenProcessor extends BaseRefactoringProcessor implements PullUpGenData {
private static final Logger LOG = Logger.getInstance(PullUpGenProcessor.class);
private final PsiClass mySourceClass;
private final PsiClass myTargetSuperClass;
private final MemberInfo[] myMembersToMove; // initialized by constructor ; returned by ???
private final DocCommentPolicy myJavaDocPolicy;
private Set<PsiMember> myMembersAfterMove = null; // created by moveMembersToBase, returned by getMovedMembers !!!
private Set<PsiMember> myMovedMembers = null; // created by moveMembersToBase, returned by getMembersToMove in origina intellij!!!
private final Map<Language, PullUpGenHelper<MemberInfo>> myProcessors = ContainerUtil.newHashMap();
private Collection<PsiClass> mySisterClasses ; //(J)
public PullUpGenProcessor(PsiClass sourceClass, Collection<PsiClass> sisterClasses, PsiClass targetSuperClass, MemberInfo[] membersToMove, DocCommentPolicy javaDocPolicy) {
super(sourceClass.getProject());
mySourceClass = sourceClass;
myTargetSuperClass = targetSuperClass;
myMembersToMove = membersToMove;
myJavaDocPolicy = javaDocPolicy;
//myProcessor = getProcessor(membersToMove[0]); this was an error, you cannot use that now because all instance variables have not been initialized
mySisterClasses = sisterClasses; //(J)
}
public PullUpGenProcessor(PsiClass sourceClass, PsiClass targetSuperClass, MemberInfo[] membersToMove, DocCommentPolicy javaDocPolicy) {
this(sourceClass, null, targetSuperClass, membersToMove, javaDocPolicy);
}
@Override
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new PullUpUsageViewDescriptor();
}
@Override
@NotNull
protected UsageInfo[] findUsages() {
final List<UsageInfo> result = new ArrayList<UsageInfo>();
for (MemberInfo memberInfo : myMembersToMove) {
final PsiMember member = memberInfo.getMember();
if (member.hasModifierProperty(PsiModifier.STATIC)) {
for (PsiReference reference : ReferencesSearch.search(member)) {
result.add(new UsageInfo(reference));
}
}
}
return result.isEmpty() ? UsageInfo.EMPTY_ARRAY : result.toArray(new UsageInfo[result.size()]);
}
@Nullable
@Override
protected String getRefactoringId() {
return "refactoring.pull.up";
}
@Nullable
@Override
protected RefactoringEventData getBeforeData() {
RefactoringEventData data = new RefactoringEventData();
data.addElement(mySourceClass);
data.addMembers(myMembersToMove, new Function<MemberInfo, PsiElement>() {
@Override
public PsiElement fun(MemberInfo info) {
return info.getMember();
}
});
return data;
}
@Nullable
@Override
protected RefactoringEventData getAfterData(UsageInfo[] usages) {
final RefactoringEventData data = new RefactoringEventData();
data.addElement(myTargetSuperClass);
return data;
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
try{
moveMembersToBase(); // initializes myMovedMembers and myMembersAfterMove
moveFieldInitializations();
for (UsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element == null) continue;
PullUpGenHelper processor = getProcessor(element);
processor.updateUsage(element);
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
processMethodsDuplicates();
}
}, ModalityState.NON_MODAL, myProject.getDisposed());
}
catch (MemberNotImplemented e) {throw new IncorrectOperationException(e.toString()) ;} // (J)
catch (AmbiguousOverloading e) {throw new IncorrectOperationException(e.toString()) ;} // (J)
}
private void processMethodsDuplicates() {
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (!myTargetSuperClass.isValid()) return;
final Query<PsiClass> search = ClassInheritorsSearch.search(myTargetSuperClass);
final Set<VirtualFile> hierarchyFiles = new HashSet<VirtualFile>();
for (PsiClass aClass : search) {
final PsiFile containingFile = aClass.getContainingFile();
if (containingFile != null) {
final VirtualFile virtualFile = containingFile.getVirtualFile();
if (virtualFile != null) {
hierarchyFiles.add(virtualFile);
}
}
}
final Set<PsiMember> methodsToSearchDuplicates = new HashSet<PsiMember>();
for (PsiMember psiMember : myMembersAfterMove) {
if (psiMember instanceof PsiMethod && psiMember.isValid() && ((PsiMethod)psiMember).getBody() != null) {
methodsToSearchDuplicates.add(psiMember);
}
}
MethodDuplicatesHandler.invokeOnScope(myProject, methodsToSearchDuplicates, new AnalysisScope(myProject, hierarchyFiles), true);
}
});
}
}, MethodDuplicatesHandler.REFACTORING_NAME, true, myProject);
}
@Override
protected String getCommandName() {
return RefactoringBundle.message("pullUp.command", DescriptiveNameUtil.getDescriptiveName(mySourceClass));
}
public void moveMembersToBase() throws IncorrectOperationException, AmbiguousOverloading, MemberNotImplemented {
myMovedMembers = ContainerUtil.newHashSet();
myMembersAfterMove = ContainerUtil.newHashSet();
assert (myMembersAfterMove != null);
assert (myMembersToMove != null && myMembersToMove.length > 0);
// (Julien) compute the set of sister methods (which have all the compatible members)
if (mySisterClasses == null )
mySisterClasses = (getProcessor(myMembersToMove[0])).getSisterClasses(myMembersToMove) ; // processor is a JavaPullUpGenHelper
// build aux sets
for (MemberInfo info : myMembersToMove) {
myMovedMembers.add(info.getMember());
}
final PsiSubstitutor substitutor = upDownSuperClassSubstitutor();
// (rem Julien) a PsiSubstitutor represents a mapping between type parameters and their values
for (MemberInfo info : myMembersToMove) {
PullUpGenHelper<MemberInfo> processor = getProcessor(info);
if (!(info.getMember() instanceof PsiClass) || info.getOverrides() == null) {
processor.setCorrectVisibility(info);
processor.encodeContextInfo(info);
}
}
assert (mySisterClasses != null);
for (PsiClass c : mySisterClasses) GenBuildUtils.alignParameters(myTargetSuperClass, c, JavaPsiFacade.getElementFactory(myProject));
// do actual move (for each member to move)
for (MemberInfo info : myMembersToMove) {
getProcessor(info).move(info, substitutor);
}
for (PsiMember member : myMembersAfterMove) { // FIXME (initialized to empty? yes but filled in JavaPullUpGenHelper)
getProcessor(member).postProcessMember(member);
final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(myProject);
((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(mySourceClass, member);
}
}
private PullUpGenHelper<MemberInfo> getProcessor(@NotNull PsiElement element) {
Language language = element.getLanguage();
return getProcessor(language);
}
private PullUpGenHelper<MemberInfo> getProcessor(Language language) { // FIXME : Reafactor that code.
assert (myMembersAfterMove != null) ;
assert (myMovedMembers != null) ;
PullUpGenHelper<MemberInfo> helper = myProcessors.get(language);
if (helper == null) {
PullUpGenHelperFactory factory = null;
for (Object f : PullUpGenHelper.INSTANCE.forKey(language)) // FIXME : find a better solution
{
if (f instanceof JavaPullUpGenHelperFactory)
factory = (PullUpGenHelperFactory) f ;
}
if ( factory == null ) {
throw new Error ("helper null (convenient PullUpGenHelperFactory not found).");
}
if (factory instanceof JavaPullUpGenHelperFactory ) {
JavaPullUpGenHelperFactory factory2 = (JavaPullUpGenHelperFactory) factory;
helper = factory2.createPullUpGenHelper(this); // at this point, myMovedMembers and myMembersAfterMove should have been initialized
}
else new Error ("JavaPullUpGenHelperFactory not found.");
myProcessors.put(language, helper);
}
return helper;
}
private PullUpGenHelper<MemberInfo> getProcessor(@NotNull MemberInfo info) {
PsiReferenceList refList = info.getSourceReferenceList();
if (refList != null) {
return getProcessor(refList.getLanguage());
}
return getProcessor(info.getMember());
}
// Julien: a PsiSubstitutor represents a mapping between type parameters and their values. (from PsiSubstitutor)
private PsiSubstitutor upDownSuperClassSubstitutor() {
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
// Julien: collect the type parameters of the source class, and map them to null in the result being built.
// null plays the role of default value (can be overwritten later).
for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(mySourceClass)) {
substitutor = substitutor.put(parameter, null);
}
final Map<PsiTypeParameter, PsiType> substitutionMap =
TypeConversionUtil.getSuperClassSubstitutor(myTargetSuperClass, mySourceClass, PsiSubstitutor.EMPTY).getSubstitutionMap();
for (PsiTypeParameter parameter : substitutionMap.keySet()) {
final PsiType type = substitutionMap.get(parameter);
final PsiClass resolvedClass = PsiUtil.resolveClassInType(type);
if (resolvedClass instanceof PsiTypeParameter) {
substitutor = substitutor.put((PsiTypeParameter)resolvedClass, JavaPsiFacade.getElementFactory(myProject).createType(parameter));
}
}
return substitutor;
}
public void moveFieldInitializations() throws IncorrectOperationException {
LOG.assertTrue(myMembersAfterMove != null);
final LinkedHashSet<PsiField> movedFields = new LinkedHashSet<PsiField>();
for (PsiMember member : myMembersAfterMove) {
if (member instanceof PsiField) {
movedFields.add((PsiField)member);
}
}
if (movedFields.isEmpty()) return;
getProcessor(myTargetSuperClass).moveFieldInitializations(movedFields);
}
public static boolean checkedInterfacesContain(Collection<? extends MemberInfoBase<? extends PsiMember>> memberInfos, PsiMethod psiMethod) {
for (MemberInfoBase<? extends PsiMember> memberInfo : memberInfos) {
if (memberInfo.isChecked() &&
memberInfo.getMember() instanceof PsiClass &&
Boolean.FALSE.equals(memberInfo.getOverrides())) {
if (((PsiClass)memberInfo.getMember()).findMethodBySignature(psiMethod, true) != null) {
return true;
}
}
}
return false;
}
@Override
public PsiClass getSourceClass() {
return mySourceClass;
}
@Override
public PsiClass getTargetClass() {
return myTargetSuperClass;
}
@Override
public DocCommentPolicy getDocCommentPolicy() {
return myJavaDocPolicy;
}
public Collection<PsiClass> getSisterClasses() {return mySisterClasses ;}
public static Set<PsiMember> convert(MemberInfo[] t){
Set<PsiMember> s = new HashSet<PsiMember>();
for (int i = 0 ; i< t.length ; i++ ) s.add(t[i].getMember());
return s;
}
@Override
public Set<PsiMember> getMembersToMove() {
return convert(myMembersToMove);
}
@Override
public Set<PsiMember> getMovedMembers() {
return myMembersAfterMove;
}
@Override
public Project getProject() {
return myProject;
}
private class PullUpUsageViewDescriptor implements UsageViewDescriptor {
@Override
public String getProcessedElementsHeader() {
return "Pull up members from";
}
@Override
@NotNull
public PsiElement[] getElements() {
return new PsiElement[]{mySourceClass};
}
@Override
public String getCodeReferencesText(int usagesCount, int filesCount) {
return "Class to pull up members to \"" + RefactoringUIUtil.getDescription(myTargetSuperClass, true) + "\"";
}
@Override
public String getCommentReferencesText(int usagesCount, int filesCount) {
return null;
}
}
}