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);
  }
  @Override
  protected void performRefactoring(@NotNull UsageInfo[] usages) {
    try {
      for (UsageInfo usage : usages) {
        if (usage instanceof SafeDeleteCustomUsageInfo) {
          ((SafeDeleteCustomUsageInfo) usage).performRefactoring();
        }
      }

      DumbService.allowStartingDumbModeInside(
          DumbModePermission.MAY_START_MODAL,
          () -> {
            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 void showUsages(final UsageInfo[] usages) {
    UsageViewPresentation presentation = new UsageViewPresentation();
    presentation.setTabText("Safe Delete Conflicts");
    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(
        () -> {
          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"));
  }
 @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(),
                 mySearchNonJava,
                 mySearchInCommentsAndStrings);
           }
         }
         handled = true;
         break;
       }
     }
     if (!handled && element instanceof PsiNamedElement) {
       findGenericElementUsages(element, usages, myElements);
       addNonCodeUsages(
           element,
           usages,
           getDefaultInsideDeletedCondition(myElements),
           mySearchNonJava,
           mySearchInCommentsAndStrings);
     }
   }
   final UsageInfo[] result = usages.toArray(new UsageInfo[usages.size()]);
   return UsageViewUtil.removeDuplicatedUsages(result);
 }
  @Override
  protected boolean preprocessUsages(@NotNull 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 instanceof SafeDeleteProcessorDelegateBase
                  ? ((SafeDeleteProcessorDelegateBase) delegate)
                      .findConflicts(element, myElements, usages)
                  : 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);
        if (!dialog.showAndGet()) {
          final int exitCode = dialog.getExitCode();
          prepareSuccessful(); // dialog is always dismissed;
          if (exitCode == UnsafeUsagesDialog.VIEW_USAGES_EXIT_CODE) {
            showUsages(
                Arrays.stream(usages)
                    .filter(
                        usage ->
                            usage instanceof SafeDeleteReferenceUsageInfo
                                && !((SafeDeleteReferenceUsageInfo) usage).isSafeDelete())
                    .toArray(UsageInfo[]::new));
          }
          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;
  }