blob: cf9cbb0be62476b3c8f7da2effdde2189b1183bd [file] [log] [blame]
/*
* Copyright 2000-2013 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.booleanIsAlwaysInverted;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.reference.*;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
public class BooleanMethodIsAlwaysInvertedInspectionBase extends GlobalJavaBatchInspectionTool {
private static final Key<Boolean> ALWAYS_INVERTED = Key.create("ALWAYS_INVERTED_METHOD");
private static boolean hasNonInvertedCalls(final RefMethod refMethod) {
final Boolean alwaysInverted = refMethod.getUserData(ALWAYS_INVERTED);
if (alwaysInverted == null) return true;
if (refMethod.isExternalOverride()) return true;
if (refMethod.isReferenced() && !alwaysInverted.booleanValue()) return true;
final Collection<RefMethod> superMethods = refMethod.getSuperMethods();
for (RefMethod superMethod : superMethods) {
if (hasNonInvertedCalls(superMethod)) return true;
}
return false;
}
private static void traverseSuperMethods(RefMethod refMethod,
GlobalJavaInspectionContext globalContext,
GlobalJavaInspectionContext.UsagesProcessor processor) {
final Collection<RefMethod> superMethods = refMethod.getSuperMethods();
for (RefMethod superMethod : superMethods) {
traverseSuperMethods(superMethod, globalContext, processor);
}
globalContext.enqueueMethodUsagesProcessor(refMethod, processor);
}
private static void checkMethodCall(RefElement refWhat, final PsiElement element) {
if (!(refWhat instanceof RefMethod)) return;
final RefMethod refMethod = (RefMethod)refWhat;
final PsiElement psiElement = refMethod.getElement();
if (!(psiElement instanceof PsiMethod)) return;
final PsiMethod psiMethod = (PsiMethod)psiElement;
if (!PsiType.BOOLEAN.equals(psiMethod.getReturnType())) return;
element.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
final PsiReferenceExpression methodExpression = call.getMethodExpression();
if (methodExpression.isReferenceTo(psiMethod)) {
if (isInvertedMethodCall(methodExpression)) return;
refMethod.putUserData(ALWAYS_INVERTED, Boolean.FALSE);
}
}
});
}
private static boolean isInvertedMethodCall(final PsiReferenceExpression methodExpression) {
final PsiPrefixExpression prefixExpression = PsiTreeUtil.getParentOfType(methodExpression, PsiPrefixExpression.class);
if (methodExpression.getQualifierExpression() instanceof PsiSuperExpression) return true; //don't flag super calls
if (prefixExpression != null) {
final IElementType tokenType = prefixExpression.getOperationTokenType();
if (tokenType.equals(JavaTokenType.EXCL)) {
return true;
}
}
return false;
}
@Override
@NotNull
public String getDisplayName() {
return InspectionsBundle.message("boolean.method.is.always.inverted.display.name");
}
@Override
@NotNull
public String getGroupDisplayName() {
return GroupNames.DATA_FLOW_ISSUES;
}
@Override
@NotNull
@NonNls
public String getShortName() {
return "BooleanMethodIsAlwaysInverted";
}
@Override
@Nullable
public RefGraphAnnotator getAnnotator(@NotNull final RefManager refManager) {
return new BooleanInvertedAnnotator();
}
@Override
public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
@NotNull AnalysisScope scope,
@NotNull final InspectionManager manager,
@NotNull final GlobalInspectionContext globalContext) {
if (refEntity instanceof RefMethod) {
RefMethod refMethod = (RefMethod)refEntity;
if (!refMethod.isReferenced()) return null;
if (hasNonInvertedCalls(refMethod)) return null;
if (!refMethod.getSuperMethods().isEmpty()) return null;
final PsiMethod psiMethod = (PsiMethod)refMethod.getElement();
final PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier();
if (psiIdentifier != null) {
final Collection<RefElement> inReferences = refMethod.getInReferences();
if (inReferences.size() == 1) {
final RefElement refElement = inReferences.iterator().next();
final PsiElement usagesContainer = refElement.getElement();
if (usagesContainer == null) return null;
if (ReferencesSearch.search(psiMethod, new LocalSearchScope(usagesContainer)).forEach(new Processor<PsiReference>() {
private final Set<PsiReference> myFoundRefs = new HashSet<PsiReference>();
@Override
public boolean process(PsiReference reference) {
myFoundRefs.add(reference);
return myFoundRefs.size() < 2;
}
})) return null;
}
return new ProblemDescriptor[]{manager.createProblemDescriptor(psiIdentifier,
InspectionsBundle
.message("boolean.method.is.always.inverted.problem.descriptor"),
(LocalQuickFix)getQuickFix(null),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)};
}
}
return null;
}
@Override
protected boolean queryExternalUsagesRequests(@NotNull final RefManager manager,
@NotNull final GlobalJavaInspectionContext context,
@NotNull final ProblemDescriptionsProcessor descriptionsProcessor) {
manager.iterate(new RefJavaVisitor() {
@Override
public void visitMethod(@NotNull final RefMethod refMethod) {
if (descriptionsProcessor.getDescriptions(refMethod) != null) { //suspicious method -> need to check external usages
final GlobalJavaInspectionContext.UsagesProcessor usagesProcessor = new GlobalJavaInspectionContext.UsagesProcessor() {
@Override
public boolean process(PsiReference psiReference) {
final PsiElement psiReferenceExpression = psiReference.getElement();
if (psiReferenceExpression instanceof PsiReferenceExpression &&
!isInvertedMethodCall((PsiReferenceExpression)psiReferenceExpression)) {
descriptionsProcessor.ignoreElement(refMethod);
}
return false;
}
};
traverseSuperMethods(refMethod, context, usagesProcessor);
}
}
});
return false;
}
private static class BooleanInvertedAnnotator extends RefGraphAnnotator {
@Override
public void onInitialize(RefElement refElement) {
if (refElement instanceof RefMethod) {
final PsiElement element = refElement.getElement();
if (!(element instanceof PsiMethod)) return;
if (((PsiMethod)element).getReturnType() != PsiType.BOOLEAN) return;
refElement.putUserData(ALWAYS_INVERTED, Boolean.TRUE); //initial mark boolean methods
}
}
@Override
public void onMarkReferenced(RefElement refWhat, RefElement refFrom, boolean referencedFromClassInitializer) {
checkMethodCall(refWhat, refFrom.getElement());
}
}
}