blob: ff1f366c42e52b169b2058e42cf84605d3a09333 [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.unneededThrows;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.reference.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AllOverridingMethodsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.containers.BidirectionalMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author max
*/
public class RedundantThrows extends GlobalJavaBatchInspectionTool {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.unneededThrows.RedundantThrows");
private static final String DISPLAY_NAME = InspectionsBundle.message("inspection.redundant.throws.display.name");
private final BidirectionalMap<String, QuickFix> myQuickFixes = new BidirectionalMap<String, QuickFix>();
@NonNls private static final String SHORT_NAME = "RedundantThrows";
@Override
@Nullable
public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
@NotNull AnalysisScope scope,
@NotNull InspectionManager manager,
@NotNull GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor processor) {
if (refEntity instanceof RefMethod) {
final RefMethod refMethod = (RefMethod)refEntity;
if (refMethod.isSyntheticJSP()) return null;
if (refMethod.hasSuperMethods()) return null;
if (refMethod.isEntry()) return null;
PsiClass[] unThrown = refMethod.getUnThrownExceptions();
if (unThrown == null) return null;
PsiMethod psiMethod = (PsiMethod)refMethod.getElement();
PsiClassType[] throwsList = psiMethod.getThrowsList().getReferencedTypes();
PsiJavaCodeReferenceElement[] throwsRefs = psiMethod.getThrowsList().getReferenceElements();
List<ProblemDescriptor> problems = null;
final PsiManager psiManager = psiMethod.getManager();
for (int i = 0; i < throwsList.length; i++) {
final PsiClassType throwsType = throwsList[i];
final String throwsClassName = throwsType.getClassName();
final PsiJavaCodeReferenceElement throwsRef = throwsRefs[i];
if (ExceptionUtil.isUncheckedException(throwsType)) continue;
if (declaredInRemotableMethod(psiMethod, throwsType)) continue;
for (PsiClass s : unThrown) {
final PsiClass throwsResolvedType = throwsType.resolve();
if (psiManager.areElementsEquivalent(s, throwsResolvedType)) {
if (problems == null) problems = new ArrayList<ProblemDescriptor>(1);
if (refMethod.isAbstract() || refMethod.getOwnerClass().isInterface()) {
problems.add(manager.createProblemDescriptor(throwsRef, InspectionsBundle.message(
"inspection.redundant.throws.problem.descriptor", "<code>#ref</code>"), getFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
false));
}
else if (!refMethod.getDerivedMethods().isEmpty()) {
problems.add(manager.createProblemDescriptor(throwsRef, InspectionsBundle.message(
"inspection.redundant.throws.problem.descriptor1", "<code>#ref</code>"), getFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
false));
}
else {
problems.add(manager.createProblemDescriptor(throwsRef, InspectionsBundle.message(
"inspection.redundant.throws.problem.descriptor2", "<code>#ref</code>"), getFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
false));
}
}
}
}
if (problems != null) {
return problems.toArray(new CommonProblemDescriptor[problems.size()]);
}
}
return null;
}
private static boolean declaredInRemotableMethod(final PsiMethod psiMethod, final PsiClassType throwsType) {
if (!throwsType.equalsToText("java.rmi.RemoteException")) return false;
PsiClass aClass = psiMethod.getContainingClass();
if (aClass == null) return false;
PsiClass remote =
JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.rmi.Remote", GlobalSearchScope.allScope(aClass.getProject()));
return remote != null && aClass.isInheritor(remote, true);
}
@Override
protected boolean queryExternalUsagesRequests(@NotNull final RefManager manager, @NotNull final GlobalJavaInspectionContext globalContext,
@NotNull final ProblemDescriptionsProcessor processor) {
manager.iterate(new RefJavaVisitor() {
@Override public void visitElement(@NotNull RefEntity refEntity) {
if (processor.getDescriptions(refEntity) != null) {
refEntity.accept(new RefJavaVisitor() {
@Override public void visitMethod(@NotNull final RefMethod refMethod) {
globalContext.enqueueDerivedMethodsProcessor(refMethod, new GlobalJavaInspectionContext.DerivedMethodsProcessor() {
@Override
public boolean process(PsiMethod derivedMethod) {
processor.ignoreElement(refMethod);
return true;
}
});
}
});
}
}
});
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;
}
private LocalQuickFix getFix(final ProblemDescriptionsProcessor processor, final String hint) {
QuickFix fix = myQuickFixes.get(hint);
if (fix == null) {
fix = new MyQuickFix(processor, hint);
if (hint != null) {
myQuickFixes.put(hint, fix);
}
}
return (LocalQuickFix)fix;
}
@Override
@Nullable
public QuickFix getQuickFix(String hint) {
return getFix(null, hint);
}
@Override
@Nullable
public String getHint(@NotNull final QuickFix fix) {
final List<String> hints = myQuickFixes.getKeysByValue(fix);
LOG.assertTrue(hints != null && hints.size() == 1);
return hints.get(0);
}
private static class MyQuickFix implements LocalQuickFix {
private final ProblemDescriptionsProcessor myProcessor;
private final String myHint;
public MyQuickFix(final ProblemDescriptionsProcessor processor, final String hint) {
myProcessor = processor;
myHint = hint;
}
@Override
@NotNull
public String getName() {
return InspectionsBundle.message("inspection.redundant.throws.remove.quickfix");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
if (myProcessor != null) {
RefElement refElement = (RefElement)myProcessor.getElement(descriptor);
if (refElement instanceof RefMethod && refElement.isValid()) {
RefMethod refMethod = (RefMethod)refElement;
final CommonProblemDescriptor[] problems = myProcessor.getDescriptions(refMethod);
if (problems != null) {
removeExcessiveThrows(refMethod, null, problems);
}
}
}
else {
final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethod.class);
if (psiMethod != null) {
removeExcessiveThrows(null, psiMethod, new CommonProblemDescriptor[]{descriptor});
}
}
}
@Override
@NotNull
public String getFamilyName() {
return getName();
}
private void removeExcessiveThrows(@Nullable RefMethod refMethod, @Nullable final PsiModifierListOwner element, final CommonProblemDescriptor[] problems) {
try {
@Nullable final PsiMethod psiMethod;
if (element == null) {
LOG.assertTrue(refMethod != null);
psiMethod = (PsiMethod)refMethod.getElement();
}
else {
psiMethod = (PsiMethod)element;
}
if (psiMethod == null) return; //invalid refMethod
final Project project = psiMethod.getProject();
final PsiManager psiManager = PsiManager.getInstance(project);
final List<PsiJavaCodeReferenceElement> refsToDelete = new ArrayList<PsiJavaCodeReferenceElement>();
for (CommonProblemDescriptor problem : problems) {
final PsiElement psiElement = ((ProblemDescriptor)problem).getPsiElement();
if (psiElement instanceof PsiJavaCodeReferenceElement) {
final PsiJavaCodeReferenceElement classRef = (PsiJavaCodeReferenceElement)psiElement;
final PsiType psiType = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createType(classRef);
removeException(refMethod, psiType, refsToDelete, psiMethod);
} else {
final PsiReferenceList throwsList = psiMethod.getThrowsList();
final PsiClassType[] classTypes = throwsList.getReferencedTypes();
for (PsiClassType classType : classTypes) {
final String text = classType.getClassName();
if (Comparing.strEqual(myHint, text)) {
removeException(refMethod, classType, refsToDelete, psiMethod);
break;
}
}
}
}
//check read-only status for derived methods
if (!FileModificationService.getInstance().preparePsiElementsForWrite(refsToDelete)) return;
for (final PsiJavaCodeReferenceElement aRefsToDelete : refsToDelete) {
if (aRefsToDelete.isValid()) {
aRefsToDelete.delete();
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
private static void removeException(final RefMethod refMethod,
final PsiType exceptionType,
final List<PsiJavaCodeReferenceElement> refsToDelete,
final PsiMethod psiMethod) {
PsiManager psiManager = psiMethod.getManager();
PsiJavaCodeReferenceElement[] refs = psiMethod.getThrowsList().getReferenceElements();
for (PsiJavaCodeReferenceElement ref : refs) {
PsiType refType = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createType(ref);
if (exceptionType.isAssignableFrom(refType)) {
refsToDelete.add(ref);
}
}
if (refMethod != null) {
for (RefMethod refDerived : refMethod.getDerivedMethods()) {
PsiModifierListOwner method = refDerived.getElement();
if (method != null) {
removeException(refDerived, exceptionType, refsToDelete, (PsiMethod)method);
}
}
} else {
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) {
removeException(null, exceptionType, refsToDelete, pair.second);
}
return true;
}
});
}
}
}
}