| /* |
| * 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()); |
| } |
| } |
| } |