Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / memberPullUp / PullUpProcessor.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.refactoring.memberPullUp;
18
19 import com.intellij.analysis.AnalysisScope;
20 import com.intellij.lang.Language;
21 import com.intellij.lang.findUsages.DescriptiveNameUtil;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ModalityState;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.progress.ProgressManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.psi.*;
29 import com.intellij.psi.search.searches.ClassInheritorsSearch;
30 import com.intellij.psi.search.searches.ReferencesSearch;
31 import com.intellij.psi.util.PsiUtil;
32 import com.intellij.psi.util.TypeConversionUtil;
33 import com.intellij.refactoring.BaseRefactoringProcessor;
34 import com.intellij.refactoring.RefactoringBundle;
35 import com.intellij.refactoring.classMembers.MemberInfoBase;
36 import com.intellij.refactoring.listeners.JavaRefactoringListenerManager;
37 import com.intellij.refactoring.listeners.RefactoringEventData;
38 import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl;
39 import com.intellij.refactoring.util.DocCommentPolicy;
40 import com.intellij.refactoring.util.RefactoringUIUtil;
41 import com.intellij.refactoring.util.classMembers.MemberInfo;
42 import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler;
43 import com.intellij.usageView.UsageInfo;
44 import com.intellij.usageView.UsageViewDescriptor;
45 import com.intellij.util.IncorrectOperationException;
46 import com.intellij.util.Query;
47 import com.intellij.util.containers.ContainerUtil;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.util.*;
52
53 public class PullUpProcessor extends BaseRefactoringProcessor implements PullUpData {
54   private static final Logger LOG = Logger.getInstance(PullUpProcessor.class);
55   @NotNull
56   private final PsiClass mySourceClass;
57   private final PsiClass myTargetSuperClass;
58   private final MemberInfo[] myMembersToMove;
59   private final DocCommentPolicy myJavaDocPolicy;
60   private Set<PsiMember> myMembersAfterMove;
61   private Set<PsiMember> myMovedMembers;
62   private final Map<Language, PullUpHelper<MemberInfo>> myProcessors = ContainerUtil.newHashMap();
63
64   public PullUpProcessor(@NotNull PsiClass sourceClass, PsiClass targetSuperClass, MemberInfo[] membersToMove, DocCommentPolicy javaDocPolicy) {
65     super(sourceClass.getProject());
66     mySourceClass = sourceClass;
67     myTargetSuperClass = targetSuperClass;
68     myMembersToMove = membersToMove;
69     myJavaDocPolicy = javaDocPolicy;
70   }
71
72   @Override
73   @NotNull
74   protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
75     return new PullUpUsageViewDescriptor();
76   }
77
78   @Override
79   @NotNull
80   protected UsageInfo[] findUsages() {
81     final List<UsageInfo> result = new ArrayList<>();
82     for (MemberInfo memberInfo : myMembersToMove) {
83       final PsiMember member = memberInfo.getMember();
84       if (member.hasModifierProperty(PsiModifier.STATIC)) {
85         for (PsiReference reference : ReferencesSearch.search(member)) {
86           result.add(new UsageInfo(reference));
87         }
88       }
89     }
90     return result.isEmpty() ? UsageInfo.EMPTY_ARRAY : result.toArray(UsageInfo.EMPTY_ARRAY);
91   }
92
93   @Nullable
94   @Override
95   protected String getRefactoringId() {
96     return "refactoring.pull.up";
97   }
98
99   @Nullable
100   @Override
101   protected RefactoringEventData getBeforeData() {
102     RefactoringEventData data = new RefactoringEventData();
103     data.addElement(mySourceClass);
104     data.addMembers(myMembersToMove, info -> info.getMember());
105     return data;
106   }
107
108   @Nullable
109   @Override
110   protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
111     final RefactoringEventData data = new RefactoringEventData();
112     data.addElement(myTargetSuperClass);
113     return data;
114   }
115
116   @Override
117   protected void performRefactoring(@NotNull UsageInfo[] usages) {
118     moveMembersToBase();
119     moveFieldInitializations();
120     for (UsageInfo usage : usages) {
121       PsiElement element = usage.getElement();
122       if (element == null) continue;
123
124       PullUpHelper<MemberInfo> processor = getProcessor(element);
125       if (processor == null) continue;
126
127       processor.updateUsage(element);
128     }
129     ApplicationManager.getApplication().invokeLater(() -> processMethodsDuplicates(), ModalityState.NON_MODAL, myProject.getDisposed());
130   }
131
132   private void processMethodsDuplicates() {
133     ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> {
134       if (!myTargetSuperClass.isValid()) return;
135       final Query<PsiClass> search = ClassInheritorsSearch.search(myTargetSuperClass);
136       final Set<VirtualFile> hierarchyFiles = new HashSet<>();
137       for (PsiClass aClass : search) {
138         final PsiFile containingFile = aClass.getContainingFile();
139         if (containingFile != null) {
140           final VirtualFile virtualFile = containingFile.getVirtualFile();
141           if (virtualFile != null) {
142             hierarchyFiles.add(virtualFile);
143           }
144         }
145       }
146       final Set<PsiMember> methodsToSearchDuplicates = new HashSet<>();
147       for (PsiMember psiMember : myMembersAfterMove) {
148         if (psiMember instanceof PsiMethod && psiMember.isValid() && ((PsiMethod)psiMember).getBody() != null) {
149           methodsToSearchDuplicates.add(psiMember);
150         }
151       }
152
153       MethodDuplicatesHandler.invokeOnScope(myProject, methodsToSearchDuplicates, new AnalysisScope(myProject, hierarchyFiles), true);
154     }), MethodDuplicatesHandler.REFACTORING_NAME, true, myProject);
155   }
156
157   @NotNull
158   @Override
159   protected String getCommandName() {
160     return RefactoringBundle.message("pullUp.command", DescriptiveNameUtil.getDescriptiveName(mySourceClass));
161   }
162
163   public void moveMembersToBase() throws IncorrectOperationException {
164     myMovedMembers = ContainerUtil.newLinkedHashSet();
165     myMembersAfterMove = ContainerUtil.newLinkedHashSet();
166
167     // build aux sets
168     for (MemberInfo info : myMembersToMove) {
169       myMovedMembers.add(info.getMember());
170     }
171
172     final PsiSubstitutor substitutor = upDownSuperClassSubstitutor();
173
174     for (MemberInfo info : myMembersToMove) {
175       PullUpHelper<MemberInfo> processor = getProcessor(info);
176
177       LOG.assertTrue(processor != null, info.getMember());
178       if (!(info.getMember() instanceof PsiClass) || info.getOverrides() == null) {
179         processor.setCorrectVisibility(info);
180         processor.encodeContextInfo(info);
181       }
182
183       processor.move(info, substitutor);
184     }
185
186     for (PsiMember member : myMembersAfterMove) {
187       PullUpHelper<MemberInfo> processor = getProcessor(member);
188       LOG.assertTrue(processor != null, member);
189
190       processor.postProcessMember(member);
191
192       final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(myProject);
193       ((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(mySourceClass, member);
194     }
195   }
196
197   @Nullable
198   private PullUpHelper<MemberInfo> getProcessor(@NotNull PsiElement element) {
199     Language language = element.getLanguage();
200     return getProcessor(language);
201   }
202
203   @Nullable
204   private PullUpHelper<MemberInfo> getProcessor(Language language) {
205     PullUpHelper<MemberInfo> helper = myProcessors.get(language);
206     if (helper == null) {
207       PullUpHelperFactory helperFactory = PullUpHelper.INSTANCE.forLanguage(language);
208       if (helperFactory == null) {
209         return null;
210       }
211       helper = helperFactory.createPullUpHelper(this);
212       myProcessors.put(language, helper);
213     }
214     return helper;
215   }
216
217   @Nullable
218   private PullUpHelper<MemberInfo> getProcessor(@NotNull MemberInfo info) {
219     PsiReferenceList refList = info.getSourceReferenceList();
220     if (refList != null) {
221       return getProcessor(refList.getLanguage());
222     }
223     return getProcessor(info.getMember());
224   }
225
226   private PsiSubstitutor upDownSuperClassSubstitutor() {
227     PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
228     for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(mySourceClass)) {
229       substitutor = substitutor.put(parameter, null);
230     }
231     final Map<PsiTypeParameter, PsiType> substitutionMap =
232       TypeConversionUtil.getSuperClassSubstitutor(myTargetSuperClass, mySourceClass, PsiSubstitutor.EMPTY).getSubstitutionMap();
233     for (PsiTypeParameter parameter : substitutionMap.keySet()) {
234       final PsiType type = substitutionMap.get(parameter);
235       final PsiClass resolvedClass = PsiUtil.resolveClassInType(type);
236       if (resolvedClass instanceof PsiTypeParameter) {
237         substitutor = substitutor.put((PsiTypeParameter)resolvedClass, JavaPsiFacade.getElementFactory(myProject).createType(parameter));
238       }
239     }
240     return substitutor;
241   }
242
243   public void moveFieldInitializations() throws IncorrectOperationException {
244     LOG.assertTrue(myMembersAfterMove != null);
245
246     final LinkedHashSet<PsiField> movedFields = new LinkedHashSet<>();
247     for (PsiMember member : myMembersAfterMove) {
248       if (member instanceof PsiField) {
249         movedFields.add((PsiField)member);
250       }
251     }
252
253     if (movedFields.isEmpty()) return;
254
255     PullUpHelper<MemberInfo> processor = getProcessor(myTargetSuperClass);
256     LOG.assertTrue(processor != null, myTargetSuperClass);
257     processor.moveFieldInitializations(movedFields);
258   }
259
260   public static boolean checkedInterfacesContain(Collection<? extends MemberInfoBase<? extends PsiMember>> memberInfos, PsiMethod psiMethod) {
261     for (MemberInfoBase<? extends PsiMember> memberInfo : memberInfos) {
262       if (memberInfo.isChecked() &&
263           memberInfo.getMember() instanceof PsiClass &&
264           Boolean.FALSE.equals(memberInfo.getOverrides())) {
265         if (((PsiClass)memberInfo.getMember()).findMethodBySignature(psiMethod, true) != null) {
266           return true;
267         }
268       }
269     }
270     return false;
271   }
272
273   @NotNull
274   @Override
275   protected Collection<? extends PsiElement> getElementsToWrite(@NotNull UsageViewDescriptor descriptor) {
276     return Collections.singletonList(mySourceClass);
277   }
278
279   @Override
280   public PsiClass getSourceClass() {
281     return mySourceClass;
282   }
283
284   @Override
285   public PsiClass getTargetClass() {
286     return myTargetSuperClass;
287   }
288
289   @Override
290   public DocCommentPolicy getDocCommentPolicy() {
291     return myJavaDocPolicy;
292   }
293
294   @Override
295   public Set<PsiMember> getMembersToMove() {
296     return myMovedMembers;
297   }
298
299   @Override
300   public Set<PsiMember> getMovedMembers() {
301     return myMembersAfterMove;
302   }
303
304   @Override
305   public Project getProject() {
306     return myProject;
307   }
308
309   private class PullUpUsageViewDescriptor implements UsageViewDescriptor {
310     @Override
311     public String getProcessedElementsHeader() {
312       return "Pull up members from class " + DescriptiveNameUtil.getDescriptiveName(mySourceClass);
313     }
314
315     @Override
316     @NotNull
317     public PsiElement[] getElements() {
318       return ContainerUtil.map(myMembersToMove, info -> info.getMember(), PsiElement.EMPTY_ARRAY);
319     }
320
321     @NotNull
322     @Override
323     public String getCodeReferencesText(int usagesCount, int filesCount) {
324       return "Class to pull up members to \"" + RefactoringUIUtil.getDescription(myTargetSuperClass, true) + "\"";
325     }
326
327     @Override
328     public String getCommentReferencesText(int usagesCount, int filesCount) {
329       return null;
330     }
331   }
332 }