|
|
|
|
@@ -16,73 +16,43 @@
|
|
|
|
|
package com.intellij.codeInspection.actions;
|
|
|
|
|
|
|
|
|
|
import com.intellij.codeInsight.ChangeContextUtil;
|
|
|
|
|
import com.intellij.codeInsight.FileModificationService;
|
|
|
|
|
import com.intellij.codeInsight.TargetElementUtil;
|
|
|
|
|
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
|
|
|
|
import com.intellij.java.JavaBundle;
|
|
|
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
|
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
|
|
|
import com.intellij.openapi.editor.Editor;
|
|
|
|
|
import com.intellij.openapi.progress.ProgressManager;
|
|
|
|
|
import com.intellij.modcommand.ActionContext;
|
|
|
|
|
import com.intellij.modcommand.ModCommand;
|
|
|
|
|
import com.intellij.modcommand.ModCommandAction;
|
|
|
|
|
import com.intellij.modcommand.Presentation;
|
|
|
|
|
import com.intellij.openapi.project.Project;
|
|
|
|
|
import com.intellij.psi.*;
|
|
|
|
|
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
|
|
|
|
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
|
|
|
|
|
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
|
|
|
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
|
|
|
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
|
|
|
|
import com.intellij.psi.search.searches.ReferencesSearch;
|
|
|
|
|
import com.intellij.psi.util.InheritanceUtil;
|
|
|
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
|
|
|
import com.intellij.util.IncorrectOperationException;
|
|
|
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction {
|
|
|
|
|
private static final Logger LOG = Logger.getInstance(ReplaceImplementsWithStaticImportAction.class);
|
|
|
|
|
|
|
|
|
|
public class ReplaceImplementsWithStaticImportAction implements ModCommandAction {
|
|
|
|
|
@Override
|
|
|
|
|
@NotNull
|
|
|
|
|
public String getText() {
|
|
|
|
|
public String getFamilyName() {
|
|
|
|
|
return JavaBundle.message("intention.text.replace.implements.with.static.import");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@NotNull
|
|
|
|
|
public String getFamilyName() {
|
|
|
|
|
return getText();
|
|
|
|
|
public @Nullable Presentation getPresentation(@NotNull ActionContext context) {
|
|
|
|
|
PsiFile file = context.file();
|
|
|
|
|
if (!(file instanceof PsiJavaFile)) return null;
|
|
|
|
|
|
|
|
|
|
if (getTargetClass(context.findLeaf()) == null) return null;
|
|
|
|
|
return Presentation.of(getFamilyName());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
|
|
|
|
if (!(file instanceof PsiJavaFile)) return false;
|
|
|
|
|
|
|
|
|
|
final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
|
|
|
|
|
if (element instanceof PsiIdentifier) {
|
|
|
|
|
final PsiElement parent = element.getParent();
|
|
|
|
|
if (parent instanceof PsiClass) {
|
|
|
|
|
return isEmptyClass(project, (PsiClass)parent) && DirectClassInheritorsSearch.search((PsiClass)parent).findFirst() != null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
final PsiReference psiReference = TargetElementUtil.findReference(editor);
|
|
|
|
|
if (psiReference == null) return false;
|
|
|
|
|
|
|
|
|
|
final PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(psiReference.getElement(), PsiReferenceList.class);
|
|
|
|
|
if (referenceList == null) return false;
|
|
|
|
|
|
|
|
|
|
final PsiClass psiClass = PsiTreeUtil.getParentOfType(referenceList, PsiClass.class);
|
|
|
|
|
if (psiClass == null) return false;
|
|
|
|
|
|
|
|
|
|
if (psiClass.getExtendsList() != referenceList && psiClass.getImplementsList() != referenceList) return false;
|
|
|
|
|
|
|
|
|
|
final PsiElement target = psiReference.resolve();
|
|
|
|
|
if (!(target instanceof PsiClass)) return false;
|
|
|
|
|
|
|
|
|
|
return isEmptyClass(project, (PsiClass)target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean isEmptyClass(Project project, PsiClass targetClass) {
|
|
|
|
|
private static boolean isEmptyClass(@NotNull PsiClass targetClass) {
|
|
|
|
|
if (!targetClass.isInterface()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
@@ -90,7 +60,7 @@ public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction
|
|
|
|
|
if (extendsList != null && extendsList.getReferencedTypes().length > 0) {
|
|
|
|
|
final List<PsiMethod> methods = new ArrayList<>(Arrays.asList(targetClass.getAllMethods()));
|
|
|
|
|
final PsiClass objectClass =
|
|
|
|
|
JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project));
|
|
|
|
|
JavaPsiFacade.getInstance(targetClass.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, targetClass.getResolveScope());
|
|
|
|
|
if (objectClass == null) return false;
|
|
|
|
|
methods.removeAll(Arrays.asList(objectClass.getMethods()));
|
|
|
|
|
if (!methods.isEmpty()) return false;
|
|
|
|
|
@@ -102,153 +72,132 @@ public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
|
|
|
|
|
final int offset = editor.getCaretModel().getOffset();
|
|
|
|
|
final PsiClass targetClass;
|
|
|
|
|
final PsiReference psiReference = TargetElementUtil.findReference(editor);
|
|
|
|
|
if (psiReference != null) {
|
|
|
|
|
final PsiElement element = psiReference.getElement();
|
|
|
|
|
|
|
|
|
|
final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
|
|
|
|
|
LOG.assertTrue(psiClass != null);
|
|
|
|
|
|
|
|
|
|
final PsiElement target = psiReference.resolve();
|
|
|
|
|
LOG.assertTrue(target instanceof PsiClass);
|
|
|
|
|
targetClass = (PsiClass)target;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
final PsiElement identifier = file.findElementAt(offset);
|
|
|
|
|
LOG.assertTrue(identifier instanceof PsiIdentifier);
|
|
|
|
|
final PsiElement element = identifier.getParent();
|
|
|
|
|
LOG.assertTrue(element instanceof PsiClass);
|
|
|
|
|
targetClass = (PsiClass)element;
|
|
|
|
|
}
|
|
|
|
|
final Map<PsiFile, Map<PsiField, Set<PsiReference>>> refs = new HashMap<>();
|
|
|
|
|
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> {
|
|
|
|
|
public @NotNull ModCommand perform(@NotNull ActionContext context) {
|
|
|
|
|
PsiElement leaf = context.findLeaf();
|
|
|
|
|
final PsiClass targetClass = Objects.requireNonNull(getTargetClass(leaf));
|
|
|
|
|
return ModCommand.psiUpdate(context, updater -> {
|
|
|
|
|
final Map<PsiJavaFile, Map<PsiField, Set<PsiElement>>> refs = new HashMap<>();
|
|
|
|
|
for (PsiField field : targetClass.getAllFields()) {
|
|
|
|
|
final PsiClass containingClass = field.getContainingClass();
|
|
|
|
|
for (PsiReference reference : ReferencesSearch.search(field)) {
|
|
|
|
|
if (reference == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
final PsiElement refElement = reference.getElement();
|
|
|
|
|
if (encodeQualifier(containingClass, reference, targetClass)) continue;
|
|
|
|
|
final PsiFile psiFile = refElement.getContainingFile();
|
|
|
|
|
if (psiFile instanceof PsiJavaFile) {
|
|
|
|
|
Map<PsiField, Set<PsiReference>> references = refs.get(psiFile);
|
|
|
|
|
if (references == null) {
|
|
|
|
|
references = new HashMap<>();
|
|
|
|
|
refs.put(psiFile, references);
|
|
|
|
|
}
|
|
|
|
|
Set<PsiReference> fieldsRefs = references.get(field);
|
|
|
|
|
if (fieldsRefs == null) {
|
|
|
|
|
fieldsRefs = new HashSet<>();
|
|
|
|
|
references.put(field, fieldsRefs);
|
|
|
|
|
}
|
|
|
|
|
fieldsRefs.add(reference);
|
|
|
|
|
if (reference == null) continue;
|
|
|
|
|
final PsiElement refElement = updater.getWritable(reference.getElement());
|
|
|
|
|
if (encodeQualifier(containingClass, refElement, targetClass)) continue;
|
|
|
|
|
PsiFile psiFile = refElement.getContainingFile();
|
|
|
|
|
if (psiFile instanceof PsiJavaFile javaFile) {
|
|
|
|
|
Map<PsiField, Set<PsiElement>> references = refs.computeIfAbsent(javaFile, k -> new HashMap<>());
|
|
|
|
|
Set<PsiElement> fieldsRefs = references.computeIfAbsent(field, k -> new HashSet<>());
|
|
|
|
|
fieldsRefs.add(refElement);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}), JavaBundle.message("replace.implements.with.static.import.field.usages.progress"), true, project)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Set<PsiJavaCodeReferenceElement> refs2Unimplement = new HashSet<>();
|
|
|
|
|
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> {
|
|
|
|
|
final Set<PsiJavaCodeReferenceElement> refs2Unimplement = new HashSet<>();
|
|
|
|
|
for (PsiClass psiClass : DirectClassInheritorsSearch.search(targetClass)) {
|
|
|
|
|
PsiFile containingFile = psiClass.getContainingFile();
|
|
|
|
|
if (!refs.containsKey(containingFile)) {
|
|
|
|
|
refs.put(containingFile, new HashMap<>());
|
|
|
|
|
}
|
|
|
|
|
if (!(containingFile instanceof PsiJavaFile javaFile)) continue;
|
|
|
|
|
refs.computeIfAbsent(updater.getWritable(javaFile), k -> new HashMap<>());
|
|
|
|
|
if (collectExtendsImplements(targetClass, psiClass.getExtendsList(), refs2Unimplement)) continue;
|
|
|
|
|
collectExtendsImplements(targetClass, psiClass.getImplementsList(), refs2Unimplement);
|
|
|
|
|
}
|
|
|
|
|
}), JavaBundle.message("progress.title.find.references.in.implement.extends.lists"), true, project)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!FileModificationService.getInstance().preparePsiElementsForWrite(refs.keySet())) return;
|
|
|
|
|
|
|
|
|
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
|
|
|
|
for (PsiFile psiFile : refs.keySet()) {
|
|
|
|
|
final Map<PsiField, Set<PsiReference>> map = refs.get(psiFile);
|
|
|
|
|
List<PsiJavaCodeReferenceElement> writableRefsToUnimplement = ContainerUtil.map(refs2Unimplement, updater::getWritable);
|
|
|
|
|
|
|
|
|
|
for (PsiJavaFile psiFile : refs.keySet()) {
|
|
|
|
|
final Map<PsiField, Set<PsiElement>> map = refs.get(psiFile);
|
|
|
|
|
for (PsiField psiField : map.keySet()) {
|
|
|
|
|
final PsiClass containingClass = psiField.getContainingClass();
|
|
|
|
|
final String fieldName = psiField.getName();
|
|
|
|
|
for (PsiReference reference : map.get(psiField)) {
|
|
|
|
|
bindReference(psiFile, psiField, containingClass, fieldName, reference, project);
|
|
|
|
|
for (PsiElement reference : map.get(psiField)) {
|
|
|
|
|
bindReference(psiFile, psiField, containingClass, fieldName, reference, context.project());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (PsiJavaCodeReferenceElement referenceElement : refs2Unimplement) {
|
|
|
|
|
for (PsiJavaCodeReferenceElement referenceElement : writableRefsToUnimplement) {
|
|
|
|
|
referenceElement.delete();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final Map<PsiJavaFile, PsiImportList> redundant = new HashMap<>();
|
|
|
|
|
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
|
|
|
|
|
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> {
|
|
|
|
|
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(context.project());
|
|
|
|
|
for (PsiFile psiFile : refs.keySet()) {
|
|
|
|
|
if (psiFile instanceof PsiJavaFile) {
|
|
|
|
|
final PsiImportList prepared = codeStyleManager.prepareOptimizeImportsResult((PsiJavaFile)psiFile);
|
|
|
|
|
if (prepared != null) {
|
|
|
|
|
redundant.put((PsiJavaFile)psiFile, prepared);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}), JavaBundle.message("progress.title.optimize.imports"), true, project)) return;
|
|
|
|
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
|
|
|
|
for (PsiJavaFile file1 : redundant.keySet()) {
|
|
|
|
|
final PsiImportList importList = redundant.get(file1);
|
|
|
|
|
if (importList != null) {
|
|
|
|
|
final PsiImportList list = file1.getImportList();
|
|
|
|
|
if (list != null) {
|
|
|
|
|
list.replace(importList);
|
|
|
|
|
if (psiFile instanceof PsiJavaFile javaFile) {
|
|
|
|
|
PsiImportList oldImports = javaFile.getImportList();
|
|
|
|
|
final PsiImportList prepared = codeStyleManager.prepareOptimizeImportsResult(javaFile);
|
|
|
|
|
if (oldImports != null && prepared != null) {
|
|
|
|
|
oldImports.replace(prepared);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean encodeQualifier(PsiClass containingClass, PsiReference reference, PsiClass targetClass) {
|
|
|
|
|
if (reference instanceof PsiReferenceExpression) {
|
|
|
|
|
final PsiElement qualifier = ((PsiReferenceExpression)reference).getQualifier();
|
|
|
|
|
@Nullable
|
|
|
|
|
private static PsiClass getTargetClass(PsiElement element) {
|
|
|
|
|
if (element instanceof PsiIdentifier) {
|
|
|
|
|
final PsiElement parent = element.getParent();
|
|
|
|
|
if (parent instanceof PsiClass psiClass) {
|
|
|
|
|
return isEmptyClass(psiClass) && DirectClassInheritorsSearch.search(psiClass).findFirst() != null ? psiClass : null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PsiJavaCodeReferenceElement ref = PsiTreeUtil.getNonStrictParentOfType(element, PsiJavaCodeReferenceElement.class);
|
|
|
|
|
if (ref == null) return null;
|
|
|
|
|
|
|
|
|
|
final PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(ref, PsiReferenceList.class);
|
|
|
|
|
if (referenceList == null) return null;
|
|
|
|
|
|
|
|
|
|
final PsiClass psiClass = PsiTreeUtil.getParentOfType(referenceList, PsiClass.class);
|
|
|
|
|
if (psiClass == null) return null;
|
|
|
|
|
|
|
|
|
|
if (psiClass.getExtendsList() != referenceList && psiClass.getImplementsList() != referenceList) return null;
|
|
|
|
|
|
|
|
|
|
final PsiElement target = ref.resolve();
|
|
|
|
|
if (!(target instanceof PsiClass targetClass)) return null;
|
|
|
|
|
|
|
|
|
|
return isEmptyClass(targetClass) ? targetClass : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean encodeQualifier(PsiClass containingClass, PsiElement reference, PsiClass targetClass) {
|
|
|
|
|
if (reference instanceof PsiReferenceExpression ref) {
|
|
|
|
|
final PsiElement qualifier = ref.getQualifier();
|
|
|
|
|
if (qualifier != null) {
|
|
|
|
|
if (qualifier instanceof PsiReferenceExpression) {
|
|
|
|
|
final PsiElement resolved = ((PsiReferenceExpression)qualifier).resolve();
|
|
|
|
|
if (resolved == containingClass || resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(targetClass, (PsiClass)resolved, true)) {
|
|
|
|
|
if (qualifier instanceof PsiReferenceExpression qualifierRef) {
|
|
|
|
|
final PsiElement resolved = qualifierRef.resolve();
|
|
|
|
|
if (resolved == containingClass ||
|
|
|
|
|
resolved instanceof PsiClass resolvedClass && InheritanceUtil.isInheritorOrSelf(targetClass, resolvedClass, true)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qualifier.putCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY,
|
|
|
|
|
ChangeContextUtil.canRemoveQualifier((PsiReferenceExpression)reference));
|
|
|
|
|
ChangeContextUtil.canRemoveQualifier(ref));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void bindReference(PsiFile psiFile,
|
|
|
|
|
private static void bindReference(PsiJavaFile psiFile,
|
|
|
|
|
PsiField psiField,
|
|
|
|
|
PsiClass containingClass,
|
|
|
|
|
String fieldName,
|
|
|
|
|
PsiReference reference,
|
|
|
|
|
PsiElement reference,
|
|
|
|
|
Project project) {
|
|
|
|
|
if (reference instanceof PsiReferenceExpression) {
|
|
|
|
|
PsiReferenceExpressionImpl.bindToElementViaStaticImport(containingClass, fieldName, ((PsiJavaFile)psiFile).getImportList());
|
|
|
|
|
final PsiElement qualifier = ((PsiReferenceExpression)reference).getQualifier();
|
|
|
|
|
if (qualifier != null) {
|
|
|
|
|
final Boolean canRemoveQualifier = qualifier.getCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY);
|
|
|
|
|
if (canRemoveQualifier != null && canRemoveQualifier.booleanValue()) {
|
|
|
|
|
qualifier.delete();
|
|
|
|
|
} else {
|
|
|
|
|
final PsiJavaCodeReferenceElement classReferenceElement =
|
|
|
|
|
JavaPsiFacade.getElementFactory(project).createReferenceExpression(containingClass);
|
|
|
|
|
qualifier.replace(classReferenceElement);
|
|
|
|
|
if (reference instanceof PsiReferenceExpression ref) {
|
|
|
|
|
PsiImportList importList = psiFile.getImportList();
|
|
|
|
|
if (importList != null) {
|
|
|
|
|
PsiReferenceExpressionImpl.bindToElementViaStaticImport(containingClass, fieldName, importList);
|
|
|
|
|
final PsiElement qualifier = ref.getQualifier();
|
|
|
|
|
if (qualifier != null) {
|
|
|
|
|
final Boolean canRemoveQualifier = qualifier.getCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY);
|
|
|
|
|
if (canRemoveQualifier != null && canRemoveQualifier.booleanValue()) {
|
|
|
|
|
qualifier.delete();
|
|
|
|
|
} else {
|
|
|
|
|
final PsiJavaCodeReferenceElement classReferenceElement =
|
|
|
|
|
JavaPsiFacade.getElementFactory(project).createReferenceExpression(containingClass);
|
|
|
|
|
qualifier.replace(classReferenceElement);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (reference.getElement() instanceof PsiDocMethodOrFieldRef){
|
|
|
|
|
reference.bindToElement(psiField); //todo refs through inheritors
|
|
|
|
|
} else if (reference instanceof PsiDocMethodOrFieldRef){
|
|
|
|
|
Objects.requireNonNull(reference.getReference()).bindToElement(psiField); //todo refs through inheritors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -257,7 +206,7 @@ public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction
|
|
|
|
|
final Set<? super PsiJavaCodeReferenceElement> refs) {
|
|
|
|
|
if (referenceList != null) {
|
|
|
|
|
for (PsiJavaCodeReferenceElement referenceElement : referenceList.getReferenceElements()) {
|
|
|
|
|
if (referenceElement.resolve() == targetClass) {
|
|
|
|
|
if (referenceElement.isReferenceTo(targetClass)) {
|
|
|
|
|
refs.add(referenceElement);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
@@ -265,9 +214,4 @@ public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean startInWriteAction() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|