| /* |
| * Copyright 2000-2014 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.safeDelete; |
| |
| import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter; |
| import com.intellij.lang.LanguageRefactoringSupport; |
| import com.intellij.lang.injection.InjectedLanguageManager; |
| import com.intellij.lang.refactoring.RefactoringSupportProvider; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.refactoring.BaseRefactoringProcessor; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.listeners.RefactoringEventData; |
| import com.intellij.refactoring.listeners.RefactoringEventListener; |
| import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteCustomUsageInfo; |
| import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceSimpleDeleteUsageInfo; |
| import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceUsageInfo; |
| import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteUsageInfo; |
| import com.intellij.refactoring.util.NonCodeSearchDescriptionLocation; |
| import com.intellij.refactoring.util.RefactoringUIUtil; |
| import com.intellij.refactoring.util.TextOccurrencesUtil; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.usageView.UsageViewUtil; |
| import com.intellij.usages.*; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.HashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author dsl |
| */ |
| public class SafeDeleteProcessor extends BaseRefactoringProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.safeDelete.SafeDeleteProcessor"); |
| private final PsiElement[] myElements; |
| private boolean mySearchInCommentsAndStrings; |
| private boolean mySearchNonJava; |
| private boolean myPreviewNonCodeUsages = true; |
| |
| private SafeDeleteProcessor(Project project, @Nullable Runnable prepareSuccessfulCallback, |
| PsiElement[] elementsToDelete, boolean isSearchInComments, boolean isSearchNonJava) { |
| super(project, prepareSuccessfulCallback); |
| myElements = elementsToDelete; |
| mySearchInCommentsAndStrings = isSearchInComments; |
| mySearchNonJava = isSearchNonJava; |
| } |
| |
| @Override |
| @NotNull |
| protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { |
| return new SafeDeleteUsageViewDescriptor(myElements); |
| } |
| |
| private static boolean isInside(PsiElement place, PsiElement[] ancestors) { |
| return isInside(place, Arrays.asList(ancestors)); |
| } |
| |
| private static boolean isInside(PsiElement place, Collection<? extends PsiElement> ancestors) { |
| for (PsiElement element : ancestors) { |
| if (isInside(place, element)) return true; |
| } |
| return false; |
| } |
| |
| public static boolean isInside (PsiElement place, PsiElement ancestor) { |
| if (ancestor instanceof PsiDirectoryContainer) { |
| final PsiDirectory[] directories = ((PsiDirectoryContainer)ancestor).getDirectories(place.getResolveScope()); |
| for (PsiDirectory directory : directories) { |
| if (isInside(place, directory)) return true; |
| } |
| } |
| |
| if (ancestor instanceof PsiFile) { |
| for (PsiFile file : ((PsiFile)ancestor).getViewProvider().getAllFiles()) { |
| if (PsiTreeUtil.isAncestor(file, place, false)) return true; |
| } |
| } |
| |
| boolean isAncestor = PsiTreeUtil.isAncestor(ancestor, place, false); |
| if (!isAncestor && ancestor instanceof PsiNameIdentifierOwner) { |
| final PsiElement nameIdentifier = ((PsiNameIdentifierOwner)ancestor).getNameIdentifier(); |
| if (nameIdentifier != null && !PsiTreeUtil.isAncestor(ancestor, nameIdentifier, true)) { |
| isAncestor = PsiTreeUtil.isAncestor(nameIdentifier.getParent(), place, false); |
| } |
| } |
| |
| if (!isAncestor) { |
| final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(place.getProject()); |
| PsiLanguageInjectionHost host = injectedLanguageManager.getInjectionHost(place); |
| while (host != null) { |
| if (PsiTreeUtil.isAncestor(ancestor, host, false)) { |
| isAncestor = true; |
| break; |
| } |
| host = injectedLanguageManager.getInjectionHost(host); |
| } |
| } |
| return isAncestor; |
| } |
| |
| @Override |
| @NotNull |
| protected UsageInfo[] findUsages() { |
| List<UsageInfo> usages = Collections.synchronizedList(new ArrayList<UsageInfo>()); |
| for (PsiElement element : myElements) { |
| boolean handled = false; |
| for(SafeDeleteProcessorDelegate delegate: Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| if (delegate.handlesElement(element)) { |
| final NonCodeUsageSearchInfo filter = delegate.findUsages(element, myElements, usages); |
| if (filter != null) { |
| for(PsiElement nonCodeUsageElement: filter.getElementsToSearch()) { |
| addNonCodeUsages(nonCodeUsageElement, usages, filter.getInsideDeletedCondition()); |
| } |
| } |
| handled = true; |
| break; |
| } |
| } |
| if (!handled && element instanceof PsiNamedElement) { |
| findGenericElementUsages(element, usages, myElements); |
| addNonCodeUsages(element, usages, getDefaultInsideDeletedCondition(myElements)); |
| } |
| } |
| final UsageInfo[] result = usages.toArray(new UsageInfo[usages.size()]); |
| return UsageViewUtil.removeDuplicatedUsages(result); |
| } |
| |
| public static Condition<PsiElement> getDefaultInsideDeletedCondition(final PsiElement[] elements) { |
| return new Condition<PsiElement>() { |
| @Override |
| public boolean value(final PsiElement usage) { |
| return !(usage instanceof PsiFile) && isInside(usage, elements); |
| } |
| }; |
| } |
| |
| public static void findGenericElementUsages(final PsiElement element, final List<UsageInfo> usages, final PsiElement[] allElementsToDelete) { |
| ReferencesSearch.search(element).forEach(new Processor<PsiReference>() { |
| @Override |
| public boolean process(final PsiReference reference) { |
| final PsiElement refElement = reference.getElement(); |
| if (!isInside(refElement, allElementsToDelete)) { |
| usages.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(refElement, element, false)); |
| } |
| return true; |
| } |
| }); |
| } |
| |
| @Override |
| protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) { |
| UsageInfo[] usages = refUsages.get(); |
| ArrayList<String> conflicts = new ArrayList<String>(); |
| |
| for (PsiElement element : myElements) { |
| for(SafeDeleteProcessorDelegate delegate: Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| if (delegate.handlesElement(element)) { |
| Collection<String> foundConflicts = delegate.findConflicts(element, myElements); |
| if (foundConflicts != null) { |
| conflicts.addAll(foundConflicts); |
| } |
| break; |
| } |
| } |
| } |
| |
| final HashMap<PsiElement,UsageHolder> elementsToUsageHolders = sortUsages(usages); |
| final Collection<UsageHolder> usageHolders = elementsToUsageHolders.values(); |
| for (UsageHolder usageHolder : usageHolders) { |
| if (usageHolder.hasUnsafeUsagesInCode()) { |
| conflicts.add(usageHolder.getDescription()); |
| } |
| } |
| |
| if (!conflicts.isEmpty()) { |
| final RefactoringEventData conflictData = new RefactoringEventData(); |
| conflictData.putUserData(RefactoringEventData.CONFLICTS_KEY, conflicts); |
| myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).conflictsDetected("refactoring.safeDelete", conflictData); |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| if (!ConflictsInTestsException.isTestIgnore()) throw new ConflictsInTestsException(conflicts); |
| } |
| else { |
| UnsafeUsagesDialog dialog = new UnsafeUsagesDialog(ArrayUtil.toStringArray(conflicts), myProject); |
| dialog.show(); |
| if (!dialog.isOK()) { |
| final int exitCode = dialog.getExitCode(); |
| prepareSuccessful(); // dialog is always dismissed |
| if (exitCode == UnsafeUsagesDialog.VIEW_USAGES_EXIT_CODE) { |
| showUsages(usages); |
| } |
| return false; |
| } |
| else { |
| myPreviewNonCodeUsages = false; |
| } |
| } |
| } |
| |
| UsageInfo[] preprocessedUsages = usages; |
| for(SafeDeleteProcessorDelegate delegate: Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| preprocessedUsages = delegate.preprocessUsages(myProject, preprocessedUsages); |
| if (preprocessedUsages == null) return false; |
| } |
| final UsageInfo[] filteredUsages = UsageViewUtil.removeDuplicatedUsages(preprocessedUsages); |
| prepareSuccessful(); // dialog is always dismissed |
| if(filteredUsages == null) { |
| return false; |
| } |
| refUsages.set(filteredUsages); |
| return true; |
| } |
| |
| private void showUsages(final UsageInfo[] usages) { |
| UsageViewPresentation presentation = new UsageViewPresentation(); |
| presentation.setTabText(RefactoringBundle.message("safe.delete.title")); |
| presentation.setTargetsNodeText(RefactoringBundle.message("attempting.to.delete.targets.node.text")); |
| presentation.setShowReadOnlyStatusAsRed(true); |
| presentation.setShowCancelButton(true); |
| presentation.setCodeUsagesString(RefactoringBundle.message("references.found.in.code")); |
| presentation.setUsagesInGeneratedCodeString(RefactoringBundle.message("references.found.in.generated.code")); |
| presentation.setNonCodeUsagesString(RefactoringBundle.message("occurrences.found.in.comments.strings.and.non.java.files")); |
| presentation.setUsagesString(RefactoringBundle.message("usageView.usagesText")); |
| |
| UsageViewManager manager = UsageViewManager.getInstance(myProject); |
| final UsageView usageView = showUsages(usages, presentation, manager); |
| usageView.addPerformOperationAction(new RerunSafeDelete(myProject, myElements, usageView), |
| RefactoringBundle.message("retry.command"), null, RefactoringBundle.message("rerun.safe.delete")); |
| usageView.addPerformOperationAction(new Runnable() { |
| @Override |
| public void run() { |
| UsageInfo[] preprocessedUsages = usages; |
| for (SafeDeleteProcessorDelegate delegate : Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| preprocessedUsages = delegate.preprocessUsages(myProject, preprocessedUsages); |
| if (preprocessedUsages == null) return; |
| } |
| final UsageInfo[] filteredUsages = UsageViewUtil.removeDuplicatedUsages(preprocessedUsages); |
| execute(filteredUsages); |
| } |
| }, "Delete Anyway", RefactoringBundle.message("usageView.need.reRun"), RefactoringBundle.message("usageView.doAction")); |
| } |
| |
| private UsageView showUsages(UsageInfo[] usages, UsageViewPresentation presentation, UsageViewManager manager) { |
| for (SafeDeleteProcessorDelegate delegate : Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| if (delegate instanceof SafeDeleteProcessorDelegateBase) { |
| final UsageView view = ((SafeDeleteProcessorDelegateBase)delegate).showUsages(usages, presentation, manager, myElements); |
| if (view != null) return view; |
| } |
| } |
| UsageTarget[] targets = new UsageTarget[myElements.length]; |
| for (int i = 0; i < targets.length; i++) { |
| targets[i] = new PsiElement2UsageTargetAdapter(myElements[i]); |
| } |
| |
| return manager.showUsages(targets, |
| UsageInfoToUsageConverter.convert(myElements, usages), |
| presentation |
| ); |
| } |
| |
| public PsiElement[] getElements() { |
| return myElements; |
| } |
| |
| private static class RerunSafeDelete implements Runnable { |
| final SmartPsiElementPointer[] myPointers; |
| private final Project myProject; |
| private final UsageView myUsageView; |
| |
| RerunSafeDelete(Project project, PsiElement[] elements, UsageView usageView) { |
| myProject = project; |
| myUsageView = usageView; |
| myPointers = new SmartPsiElementPointer[elements.length]; |
| for (int i = 0; i < elements.length; i++) { |
| PsiElement element = elements[i]; |
| myPointers[i] = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(element); |
| } |
| } |
| |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| myUsageView.close(); |
| ArrayList<PsiElement> elements = new ArrayList<PsiElement>(); |
| for (SmartPsiElementPointer pointer : myPointers) { |
| final PsiElement element = pointer.getElement(); |
| if (element != null) { |
| elements.add(element); |
| } |
| } |
| if(!elements.isEmpty()) { |
| SafeDeleteHandler.invoke(myProject, PsiUtilCore.toPsiElementArray(elements), true); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * @param usages |
| * @return Map from elements to UsageHolders |
| */ |
| private static HashMap<PsiElement,UsageHolder> sortUsages(UsageInfo[] usages) { |
| HashMap<PsiElement,UsageHolder> result = new HashMap<PsiElement, UsageHolder>(); |
| |
| for (final UsageInfo usage : usages) { |
| if (usage instanceof SafeDeleteUsageInfo) { |
| final PsiElement referencedElement = ((SafeDeleteUsageInfo)usage).getReferencedElement(); |
| if (!result.containsKey(referencedElement)) { |
| result.put(referencedElement, new UsageHolder(referencedElement, usages)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| |
| @Override |
| protected void refreshElements(PsiElement[] elements) { |
| LOG.assertTrue(elements.length == myElements.length); |
| System.arraycopy(elements, 0, myElements, 0, elements.length); |
| } |
| |
| @Override |
| protected boolean isPreviewUsages(UsageInfo[] usages) { |
| if(myPreviewNonCodeUsages && UsageViewUtil.reportNonRegularUsages(usages, myProject)) { |
| return true; |
| } |
| |
| return super.isPreviewUsages(filterToBeDeleted(usages)); |
| } |
| |
| private static UsageInfo[] filterToBeDeleted(UsageInfo[] infos) { |
| ArrayList<UsageInfo> list = new ArrayList<UsageInfo>(); |
| for (UsageInfo info : infos) { |
| if (!(info instanceof SafeDeleteReferenceUsageInfo) || ((SafeDeleteReferenceUsageInfo) info).isSafeDelete()) { |
| list.add(info); |
| } |
| } |
| return list.toArray(new UsageInfo[list.size()]); |
| } |
| |
| @Nullable |
| @Override |
| protected RefactoringEventData getBeforeData() { |
| final RefactoringEventData beforeData = new RefactoringEventData(); |
| beforeData.addElements(myElements); |
| return beforeData; |
| } |
| |
| @Nullable |
| @Override |
| protected String getRefactoringId() { |
| return "refactoring.safeDelete"; |
| } |
| |
| @Override |
| protected void performRefactoring(UsageInfo[] usages) { |
| try { |
| for (UsageInfo usage : usages) { |
| if (usage instanceof SafeDeleteCustomUsageInfo) { |
| ((SafeDeleteCustomUsageInfo) usage).performRefactoring(); |
| } |
| } |
| |
| for (PsiElement element : myElements) { |
| for(SafeDeleteProcessorDelegate delegate: Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| if (delegate.handlesElement(element)) { |
| delegate.prepareForDeletion(element); |
| } |
| } |
| |
| element.delete(); |
| } |
| } catch (IncorrectOperationException e) { |
| RefactoringUIUtil.processIncorrectOperation(myProject, e); |
| } |
| } |
| |
| private String calcCommandName() { |
| return RefactoringBundle.message("safe.delete.command", RefactoringUIUtil.calculatePsiElementDescriptionList(myElements)); |
| } |
| |
| private String myCachedCommandName = null; |
| @Override |
| protected String getCommandName() { |
| if (myCachedCommandName == null) { |
| myCachedCommandName = calcCommandName(); |
| } |
| return myCachedCommandName; |
| } |
| |
| |
| private void addNonCodeUsages(final PsiElement element, List<UsageInfo> usages, @Nullable final Condition<PsiElement> insideElements) { |
| TextOccurrencesUtil.UsageInfoFactory nonCodeUsageFactory = new TextOccurrencesUtil.UsageInfoFactory() { |
| @Override |
| public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) { |
| if (insideElements != null && insideElements.value(usage)) { |
| return null; |
| } |
| return new SafeDeleteReferenceSimpleDeleteUsageInfo(usage, element, startOffset, endOffset, true, false); |
| } |
| }; |
| if (mySearchInCommentsAndStrings) { |
| String stringToSearch = ElementDescriptionUtil.getElementDescription(element, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS); |
| TextOccurrencesUtil.addUsagesInStringsAndComments(element, stringToSearch, usages, nonCodeUsageFactory); |
| } |
| if (mySearchNonJava) { |
| String stringToSearch = ElementDescriptionUtil.getElementDescription(element, NonCodeSearchDescriptionLocation.NON_JAVA); |
| TextOccurrencesUtil.addTextOccurences(element, stringToSearch, GlobalSearchScope.projectScope(myProject), usages, nonCodeUsageFactory); |
| } |
| } |
| |
| @Override |
| protected boolean isToBeChanged(UsageInfo usageInfo) { |
| if (usageInfo instanceof SafeDeleteReferenceUsageInfo) { |
| return ((SafeDeleteReferenceUsageInfo)usageInfo).isSafeDelete() && super.isToBeChanged(usageInfo); |
| } |
| return super.isToBeChanged(usageInfo); |
| } |
| |
| public static boolean validElement(@NotNull PsiElement element) { |
| if (element instanceof PsiFile) return true; |
| if (!element.isPhysical()) return false; |
| final RefactoringSupportProvider provider = LanguageRefactoringSupport.INSTANCE.forLanguage(element.getLanguage()); |
| return provider.isSafeDeleteAvailable(element); |
| } |
| |
| public static SafeDeleteProcessor createInstance(Project project, @Nullable Runnable prepareSuccessfulCallback, |
| PsiElement[] elementsToDelete, boolean isSearchInComments, boolean isSearchNonJava) { |
| return new SafeDeleteProcessor(project, prepareSuccessfulCallback, elementsToDelete, isSearchInComments, isSearchNonJava); |
| } |
| |
| public static SafeDeleteProcessor createInstance(Project project, @Nullable Runnable prepareSuccessfulCallBack, |
| PsiElement[] elementsToDelete, boolean isSearchInComments, boolean isSearchNonJava, |
| boolean askForAccessors) { |
| ArrayList<PsiElement> elements = new ArrayList<PsiElement>(Arrays.asList(elementsToDelete)); |
| HashSet<PsiElement> elementsToDeleteSet = new HashSet<PsiElement>(Arrays.asList(elementsToDelete)); |
| |
| for (PsiElement psiElement : elementsToDelete) { |
| for(SafeDeleteProcessorDelegate delegate: Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { |
| if (delegate.handlesElement(psiElement)) { |
| Collection<PsiElement> addedElements = delegate.getAdditionalElementsToDelete(psiElement, elementsToDeleteSet, askForAccessors); |
| if (addedElements != null) { |
| elements.addAll(addedElements); |
| } |
| break; |
| } |
| } |
| } |
| |
| return new SafeDeleteProcessor(project, prepareSuccessfulCallBack, |
| PsiUtilCore.toPsiElementArray(elements), |
| isSearchInComments, isSearchNonJava); |
| } |
| |
| public boolean isSearchInCommentsAndStrings() { |
| return mySearchInCommentsAndStrings; |
| } |
| |
| public void setSearchInCommentsAndStrings(boolean searchInCommentsAndStrings) { |
| mySearchInCommentsAndStrings = searchInCommentsAndStrings; |
| } |
| |
| public boolean isSearchNonJava() { |
| return mySearchNonJava; |
| } |
| |
| public void setSearchNonJava(boolean searchNonJava) { |
| mySearchNonJava = searchNonJava; |
| } |
| |
| @Override |
| protected boolean skipNonCodeUsages() { |
| return true; |
| } |
| } |