mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 22:17:05 +07:00
420 lines
16 KiB
Java
420 lines
16 KiB
Java
/*
|
|
* 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.emptyMethod;
|
|
|
|
import com.intellij.ToolExtensionPoints;
|
|
import com.intellij.analysis.AnalysisScope;
|
|
import com.intellij.codeInsight.AnnotationUtil;
|
|
import com.intellij.codeInsight.daemon.GroupNames;
|
|
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
|
import com.intellij.codeInspection.*;
|
|
import com.intellij.codeInspection.reference.*;
|
|
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
|
import com.intellij.codeInspection.util.SpecialAnnotationsUtil;
|
|
import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase;
|
|
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.*;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.search.searches.AllOverridingMethodsSearch;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.psi.util.PsiUtilCore;
|
|
import com.intellij.refactoring.safeDelete.SafeDeleteHandler;
|
|
import com.intellij.util.Processor;
|
|
import com.intellij.util.Query;
|
|
import com.intellij.util.containers.BidirectionalMap;
|
|
import org.jdom.Element;
|
|
import org.jetbrains.annotations.NonNls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* @author max
|
|
*/
|
|
public class EmptyMethodInspection extends GlobalJavaBatchInspectionTool {
|
|
private static final String DISPLAY_NAME = InspectionsBundle.message("inspection.empty.method.display.name");
|
|
@NonNls private static final String SHORT_NAME = "EmptyMethod";
|
|
|
|
private final BidirectionalMap<Boolean, QuickFix> myQuickFixes = new BidirectionalMap<Boolean, QuickFix>();
|
|
|
|
public final JDOMExternalizableStringList EXCLUDE_ANNOS = new JDOMExternalizableStringList();
|
|
@SuppressWarnings("PublicField")
|
|
public boolean commentsAreContent = false;
|
|
@NonNls private static final String QUICK_FIX_NAME = InspectionsBundle.message("inspection.empty.method.delete.quickfix");
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.emptyMethod.EmptyMethodInspection");
|
|
|
|
@Override
|
|
@Nullable
|
|
public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
|
|
@NotNull AnalysisScope scope,
|
|
@NotNull InspectionManager manager,
|
|
@NotNull GlobalInspectionContext globalContext,
|
|
@NotNull ProblemDescriptionsProcessor processor) {
|
|
if (!(refEntity instanceof RefMethod)) {
|
|
return null;
|
|
}
|
|
final RefMethod refMethod = (RefMethod)refEntity;
|
|
|
|
if (!isBodyEmpty(refMethod)) return null;
|
|
if (refMethod.isConstructor()) return null;
|
|
if (refMethod.isSyntheticJSP()) return null;
|
|
|
|
for (RefMethod refSuper : refMethod.getSuperMethods()) {
|
|
if (checkElement(refSuper, scope, manager, globalContext, processor) != null) return null;
|
|
}
|
|
|
|
String message = null;
|
|
boolean needToDeleteHierarchy = false;
|
|
RefMethod refSuper = findSuperWithBody(refMethod);
|
|
if (refMethod.isOnlyCallsSuper() && !refMethod.isFinal()) {
|
|
final RefJavaUtil refUtil = RefJavaUtil.getInstance();
|
|
if (refSuper != null && Comparing.strEqual(refMethod.getAccessModifier(), refSuper.getAccessModifier())){
|
|
if (Comparing.strEqual(refSuper.getAccessModifier(), PsiModifier.PROTECTED) //protected modificator gives access to method in another package
|
|
&& !Comparing.strEqual(refUtil.getPackageName(refSuper), refUtil.getPackageName(refMethod))) return null;
|
|
final PsiModifierListOwner modifierListOwner = refMethod.getElement();
|
|
if (modifierListOwner != null) {
|
|
final PsiModifierList list = modifierListOwner.getModifierList();
|
|
if (list != null) {
|
|
final PsiModifierListOwner supMethod = refSuper.getElement();
|
|
if (supMethod != null) {
|
|
final PsiModifierList superModifiedList = supMethod.getModifierList();
|
|
LOG.assertTrue(superModifiedList != null);
|
|
if (list.hasModifierProperty(PsiModifier.SYNCHRONIZED) && !superModifiedList.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (refSuper == null || refUtil.compareAccess(refMethod.getAccessModifier(), refSuper.getAccessModifier()) <= 0) {
|
|
message = InspectionsBundle.message("inspection.empty.method.problem.descriptor");
|
|
}
|
|
}
|
|
else if (refMethod.hasBody() && hasEmptySuperImplementation(refMethod)) {
|
|
|
|
message = InspectionsBundle.message("inspection.empty.method.problem.descriptor1");
|
|
}
|
|
else if (areAllImplementationsEmpty(refMethod) && refSuper == null) {
|
|
if (refMethod.hasBody()) {
|
|
if (refMethod.getDerivedMethods().isEmpty()) {
|
|
if (refMethod.getSuperMethods().isEmpty()) {
|
|
message = InspectionsBundle.message("inspection.empty.method.problem.descriptor2");
|
|
}
|
|
}
|
|
else {
|
|
needToDeleteHierarchy = true;
|
|
message = InspectionsBundle.message("inspection.empty.method.problem.descriptor3");
|
|
}
|
|
}
|
|
else {
|
|
if (!refMethod.getDerivedMethods().isEmpty()) {
|
|
needToDeleteHierarchy = true;
|
|
message = InspectionsBundle.message("inspection.empty.method.problem.descriptor4");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (message != null) {
|
|
final ArrayList<LocalQuickFix> fixes = new ArrayList<LocalQuickFix>();
|
|
fixes.add(getFix(processor, needToDeleteHierarchy));
|
|
SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(refMethod.getElement(), new Processor<String>() {
|
|
@Override
|
|
public boolean process(final String qualifiedName) {
|
|
fixes.add(SpecialAnnotationsUtilBase.createAddToSpecialAnnotationsListQuickFix(
|
|
QuickFixBundle.message("fix.add.special.annotation.text", qualifiedName),
|
|
QuickFixBundle.message("fix.add.special.annotation.family"),
|
|
EXCLUDE_ANNOS, qualifiedName, refMethod.getElement()));
|
|
return true;
|
|
}
|
|
});
|
|
|
|
final ProblemDescriptor descriptor = manager.createProblemDescriptor(refMethod.getElement().getNavigationElement(), message, false,
|
|
fixes.toArray(new LocalQuickFix[fixes.size()]),
|
|
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
|
return new ProblemDescriptor[]{descriptor};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private boolean isBodyEmpty(final RefMethod refMethod) {
|
|
if (!refMethod.isBodyEmpty()) {
|
|
return false;
|
|
}
|
|
final PsiModifierListOwner owner = refMethod.getElement();
|
|
if (owner == null) {
|
|
return false;
|
|
}
|
|
if (AnnotationUtil.isAnnotated(owner, EXCLUDE_ANNOS)) {
|
|
return false;
|
|
}
|
|
for (final Object extension : Extensions.getExtensions(ToolExtensionPoints.EMPTY_METHOD_TOOL)) {
|
|
if (((Condition<RefMethod>) extension).value(refMethod)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (commentsAreContent && PsiTreeUtil.findChildOfType(owner, PsiComment.class) != null) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Nullable
|
|
private static RefMethod findSuperWithBody(RefMethod refMethod) {
|
|
for (RefMethod refSuper : refMethod.getSuperMethods()) {
|
|
if (refSuper.hasBody()) return refSuper;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private boolean areAllImplementationsEmpty(RefMethod refMethod) {
|
|
if (refMethod.hasBody() && !isBodyEmpty(refMethod)) return false;
|
|
|
|
for (RefMethod refDerived : refMethod.getDerivedMethods()) {
|
|
if (!areAllImplementationsEmpty(refDerived)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private boolean hasEmptySuperImplementation(RefMethod refMethod) {
|
|
for (RefMethod refSuper : refMethod.getSuperMethods()) {
|
|
if (refSuper.hasBody() && isBodyEmpty(refSuper)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected boolean queryExternalUsagesRequests(@NotNull final RefManager manager,
|
|
@NotNull final GlobalJavaInspectionContext context,
|
|
@NotNull final ProblemDescriptionsProcessor descriptionsProcessor) {
|
|
manager.iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(@NotNull RefEntity refEntity) {
|
|
if (refEntity instanceof RefElement && descriptionsProcessor.getDescriptions(refEntity) != null) {
|
|
refEntity.accept(new RefJavaVisitor() {
|
|
@Override public void visitMethod(@NotNull final RefMethod refMethod) {
|
|
context.enqueueDerivedMethodsProcessor(refMethod, new GlobalJavaInspectionContext.DerivedMethodsProcessor() {
|
|
@Override
|
|
public boolean process(PsiMethod derivedMethod) {
|
|
PsiCodeBlock body = derivedMethod.getBody();
|
|
if (body == null) return true;
|
|
if (body.getStatements().length == 0) return true;
|
|
if (RefJavaUtil.getInstance().isMethodOnlyCallsSuper(derivedMethod)) return true;
|
|
descriptionsProcessor.ignoreElement(refMethod);
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getDisplayName() {
|
|
return DISPLAY_NAME;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getGroupDisplayName() {
|
|
return GroupNames.DECLARATION_REDUNDANCY;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getShortName() {
|
|
return SHORT_NAME;
|
|
}
|
|
|
|
@Override
|
|
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
|
if (!EXCLUDE_ANNOS.isEmpty() || commentsAreContent) {
|
|
super.writeSettings(node);
|
|
}
|
|
}
|
|
|
|
private LocalQuickFix getFix(final ProblemDescriptionsProcessor processor, final boolean needToDeleteHierarchy) {
|
|
QuickFix fix = myQuickFixes.get(needToDeleteHierarchy);
|
|
if (fix == null) {
|
|
fix = new DeleteMethodQuickFix(processor, needToDeleteHierarchy);
|
|
myQuickFixes.put(needToDeleteHierarchy, fix);
|
|
return (LocalQuickFix)fix;
|
|
}
|
|
return (LocalQuickFix)fix;
|
|
}
|
|
|
|
@Override
|
|
public String getHint(@NotNull final QuickFix fix) {
|
|
final List<Boolean> list = myQuickFixes.getKeysByValue(fix);
|
|
if (list != null) {
|
|
LOG.assertTrue(list.size() == 1);
|
|
return String.valueOf(list.get(0));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
@Nullable
|
|
public LocalQuickFix getQuickFix(final String hint) {
|
|
return new DeleteMethodIntention(hint);
|
|
}
|
|
|
|
@Override
|
|
@Nullable
|
|
public JComponent createOptionsPanel() {
|
|
final JPanel listPanel = SpecialAnnotationsUtil
|
|
.createSpecialAnnotationsListControl(EXCLUDE_ANNOS, InspectionsBundle.message("special.annotations.annotations.list"));
|
|
|
|
final JPanel panel = new JPanel(new BorderLayout(2, 2));
|
|
panel.add(new SingleCheckboxOptionsPanel("Comments and javadoc count as content", this, "commentsAreContent"), BorderLayout.NORTH);
|
|
panel.add(listPanel, BorderLayout.CENTER);
|
|
return panel;
|
|
}
|
|
|
|
private class DeleteMethodIntention implements LocalQuickFix {
|
|
private final String myHint;
|
|
|
|
public DeleteMethodIntention(final String hint) {
|
|
myHint = hint;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getName() {
|
|
return QUICK_FIX_NAME;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getFamilyName() {
|
|
return QUICK_FIX_NAME;
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
|
|
final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethod.class, false);
|
|
if (psiMethod != null) {
|
|
final List<PsiElement> psiElements = new ArrayList<PsiElement>();
|
|
psiElements.add(psiMethod);
|
|
if (Boolean.valueOf(myHint).booleanValue()) {
|
|
final Query<Pair<PsiMethod, PsiMethod>> query = AllOverridingMethodsSearch.search(psiMethod.getContainingClass());
|
|
query.forEach(new Processor<Pair<PsiMethod, PsiMethod>>() {
|
|
@Override
|
|
public boolean process(final Pair<PsiMethod, PsiMethod> pair) {
|
|
if (pair.first == psiMethod) {
|
|
psiElements.add(pair.second);
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
SafeDeleteHandler.invoke(project, PsiUtilCore.toPsiElementArray(psiElements), false);
|
|
}
|
|
}, project.getDisposed());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private class DeleteMethodQuickFix implements LocalQuickFix, BatchQuickFix<CommonProblemDescriptor> {
|
|
private final ProblemDescriptionsProcessor myProcessor;
|
|
private final boolean myNeedToDeleteHierarchy;
|
|
|
|
public DeleteMethodQuickFix(final ProblemDescriptionsProcessor processor, final boolean needToDeleteHierarchy) {
|
|
myProcessor = processor;
|
|
myNeedToDeleteHierarchy = needToDeleteHierarchy;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getName() {
|
|
return QUICK_FIX_NAME;
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) {
|
|
applyFix(project, new ProblemDescriptor[]{descriptor}, new ArrayList<PsiElement>(), null);
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getFamilyName() {
|
|
return getName();
|
|
}
|
|
|
|
private void deleteHierarchy(RefMethod refMethod, List<PsiElement> result) {
|
|
Collection<RefMethod> derivedMethods = refMethod.getDerivedMethods();
|
|
RefMethod[] refMethods = derivedMethods.toArray(new RefMethod[derivedMethods.size()]);
|
|
for (RefMethod refDerived : refMethods) {
|
|
deleteMethod(refDerived, result);
|
|
}
|
|
deleteMethod(refMethod, result);
|
|
}
|
|
|
|
private void deleteMethod(RefMethod refMethod, List<PsiElement> result) {
|
|
PsiElement psiElement = refMethod.getElement();
|
|
if (psiElement == null) return;
|
|
if (!result.contains(psiElement)) result.add(psiElement);
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull final Project project,
|
|
@NotNull final CommonProblemDescriptor[] descriptors,
|
|
@NotNull final List<PsiElement> psiElementsToIgnore,
|
|
@Nullable final Runnable refreshViews) {
|
|
for (CommonProblemDescriptor descriptor : descriptors) {
|
|
RefElement refElement = (RefElement)myProcessor.getElement(descriptor);
|
|
if (refElement.isValid() && refElement instanceof RefMethod) {
|
|
RefMethod refMethod = (RefMethod)refElement;
|
|
if (myNeedToDeleteHierarchy) {
|
|
deleteHierarchy(refMethod, psiElementsToIgnore);
|
|
}
|
|
else {
|
|
deleteMethod(refMethod, psiElementsToIgnore);
|
|
}
|
|
}
|
|
}
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
SafeDeleteHandler.invoke(project, PsiUtilCore.toPsiElementArray(psiElementsToIgnore), false, refreshViews);
|
|
}
|
|
}, project.getDisposed());
|
|
}
|
|
}
|
|
}
|