/* * 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.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.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressManager; 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 org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.util.*; public class ReplaceImplementsWithStaticImportAction extends BaseIntentionAction { private static final Logger LOG = Logger.getInstance(ReplaceImplementsWithStaticImportAction.class); @NonNls private static final String FIND_CONSTANT_FIELD_USAGES = "Find Constant Field Usages..."; @Override @NotNull public String getText() { return "Replace implements with static import"; } @Override @NotNull public String getFamilyName() { return getText(); } @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 == null || !(target instanceof PsiClass)) return false; return isEmptyClass(project, (PsiClass)target); } private static boolean isEmptyClass(Project project, PsiClass targetClass) { if (!targetClass.isInterface()) { return false; } final PsiReferenceList extendsList = targetClass.getExtendsList(); 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)); if (objectClass == null) return false; methods.removeAll(Arrays.asList(objectClass.getMethods())); if (!methods.isEmpty()) return false; } else if (targetClass.getMethods().length > 0) { return false; } return targetClass.getAllFields().length > 0; } @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(() -> { 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); } } } }), FIND_CONSTANT_FIELD_USAGES, true, project)) { return; } final Set<PsiJavaCodeReferenceElement> refs2Unimplement = new HashSet<>(); if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> { for (PsiClass psiClass : DirectClassInheritorsSearch.search(targetClass)) { PsiFile containingFile = psiClass.getContainingFile(); if (!refs.containsKey(containingFile)) { refs.put(containingFile, new HashMap<>()); } if (collectExtendsImplements(targetClass, psiClass.getExtendsList(), refs2Unimplement)) continue; collectExtendsImplements(targetClass, psiClass.getImplementsList(), refs2Unimplement); } }), "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); 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 (PsiJavaCodeReferenceElement referenceElement : refs2Unimplement) { referenceElement.delete(); } }); final Map<PsiJavaFile, PsiImportList> redundant = new HashMap<>(); final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ApplicationManager.getApplication().runReadAction(() -> { for (PsiFile psiFile : refs.keySet()) { if (psiFile instanceof PsiJavaFile) { final PsiImportList prepared = codeStyleManager.prepareOptimizeImportsResult((PsiJavaFile)psiFile); if (prepared != null) { redundant.put((PsiJavaFile)psiFile, prepared); } } } }), "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); } } } }); } private static boolean encodeQualifier(PsiClass containingClass, PsiReference reference, PsiClass targetClass) { if (reference instanceof PsiReferenceExpression) { final PsiElement qualifier = ((PsiReferenceExpression)reference).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)) { return true; } } qualifier.putCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY, ChangeContextUtil.canRemoveQualifier((PsiReferenceExpression)reference)); } } return false; } private static void bindReference(PsiFile psiFile, PsiField psiField, PsiClass containingClass, String fieldName, PsiReference 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); } } } else if (reference.getElement() instanceof PsiDocMethodOrFieldRef){ reference.bindToElement(psiField); //todo refs through inheritors } } private static boolean collectExtendsImplements(final PsiClass targetClass, final PsiReferenceList referenceList, final Set<PsiJavaCodeReferenceElement> refs) { if (referenceList != null) { for (PsiJavaCodeReferenceElement referenceElement : referenceList.getReferenceElements()) { if (referenceElement.resolve() == targetClass) { refs.add(referenceElement); return true; } } } return false; } @Override public boolean startInWriteAction() { return false; } }