blob: 5ced0c4fb76697816cc25d796d53a12aa7ad8040 [file] [log] [blame]
/*
* 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.TargetElementUtilBase;
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 = TargetElementUtilBase.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<PsiMethod>(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 {
if (!FileModificationService.getInstance().preparePsiElementForWrite(file)) return;
final int offset = editor.getCaretModel().getOffset();
final PsiReference psiReference = file.findReferenceAt(offset);
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);
final PsiClass targetClass = (PsiClass)target;
new WriteCommandAction(project, getText()) {
@Override
protected void run(@NotNull Result result) throws Throwable {
for (PsiField constField : targetClass.getAllFields()) {
final String fieldName = constField.getName();
final PsiClass containingClass = constField.getContainingClass();
for (PsiReference ref : ReferencesSearch.search(constField)) {
final PsiElement psiElement = ref.getElement();
if (ref instanceof PsiReferenceExpression) {
final PsiElement qualifier = ((PsiReferenceExpression)ref).getQualifier();
if (qualifier != null) {
if (qualifier instanceof PsiReferenceExpression) {
final PsiElement resolved = ((PsiReferenceExpression)qualifier).resolve();
if (resolved instanceof PsiClass && !InheritanceUtil.isInheritorOrSelf(psiClass, (PsiClass)resolved, true)) {
continue;
}
}
qualifier.putCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY,
ChangeContextUtil.canRemoveQualifier((PsiReferenceExpression)ref));
}
}
bindReference(psiElement.getContainingFile(), constField, containingClass, fieldName, ref, project);
}
}
element.delete();
JavaCodeStyleManager.getInstance(project).optimizeImports(file);
}
}.execute();
}
else {
final PsiElement identifier = file.findElementAt(offset);
LOG.assertTrue(identifier instanceof PsiIdentifier);
final PsiElement element = identifier.getParent();
LOG.assertTrue(element instanceof PsiClass);
final PsiClass targetClass = (PsiClass)element;
final Map<PsiFile, Map<PsiField, Set<PsiReference>>> refs = new HashMap<PsiFile, Map<PsiField, Set<PsiReference>>>();
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
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<PsiField, Set<PsiReference>>();
refs.put(psiFile, references);
}
Set<PsiReference> fieldsRefs = references.get(field);
if (fieldsRefs == null) {
fieldsRefs = new HashSet<PsiReference>();
references.put(field, fieldsRefs);
}
fieldsRefs.add(reference);
}
}
}
}
});
}
}, FIND_CONSTANT_FIELD_USAGES, true, project)) {
return;
}
final Set<PsiJavaCodeReferenceElement> refs2Unimplement = new HashSet<PsiJavaCodeReferenceElement>();
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (PsiClass psiClass : DirectClassInheritorsSearch.search(targetClass)) {
PsiFile containingFile = psiClass.getContainingFile();
if (!refs.containsKey(containingFile)) {
refs.put(containingFile, new HashMap<PsiField, Set<PsiReference>>());
}
if (collectExtendsImplements(targetClass, psiClass.getExtendsList(), refs2Unimplement)) continue;
collectExtendsImplements(targetClass, psiClass.getImplementsList(), refs2Unimplement);
}
}
});
}
}, "Find references in implement/extends lists...", true, project)) {
return;
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
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 Set<SmartPsiElementPointer<PsiImportStatementBase>> redundant = new HashSet<SmartPsiElementPointer<PsiImportStatementBase>>();
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
final SmartPointerManager pointerManager = SmartPointerManager.getInstance(project);
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable(){
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (PsiFile psiFile : refs.keySet()) {
final Collection<PsiImportStatementBase> red = codeStyleManager.findRedundantImports((PsiJavaFile)psiFile);
if (red != null) {
for (PsiImportStatementBase statementBase : red) {
redundant.add(pointerManager.createSmartPsiElementPointer(statementBase));
}
}
}
}
});
}
}, "Collect redundant imports...", true, project)) return;
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (SmartPsiElementPointer<PsiImportStatementBase> pointer : redundant) {
final PsiImportStatementBase statementBase = pointer.getElement();
if (statementBase != null) statementBase.delete();
}
}
});
}
}
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;
}
}