/* * Copyright 2005-2016 Bas Leijdekkers * * 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.siyeh.ig.abstraction; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.psi.*; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Processor; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.DeclarationSearchUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; public class MethodOnlyUsedFromInnerClassInspection extends BaseInspection { @SuppressWarnings({"PublicField"}) public boolean ignoreMethodsAccessedFromAnonymousClass = false; @SuppressWarnings({"PublicField"}) public boolean ignoreStaticMethodsFromNonStaticInnerClass = false; @SuppressWarnings({"PublicField"}) public boolean onlyReportStaticMethods = false; @Override @NotNull public String getDisplayName() { return InspectionGadgetsBundle.message("method.only.used.from.inner.class.display.name"); } @Override @NotNull protected String buildErrorString(Object... infos) { final PsiNamedElement element = (PsiNamedElement)infos[0]; final String name = element.getName(); if (infos.length > 1) { if (Boolean.TRUE.equals(infos[1])) { return InspectionGadgetsBundle.message("method.only.used.from.inner.class.problem.descriptor.anonymous.extending", name); } return InspectionGadgetsBundle.message("method.only.used.from.inner.class.problem.descriptor.anonymous.implementing", name); } return InspectionGadgetsBundle.message( "method.only.used.from.inner.class.problem.descriptor", name); } @Override @Nullable public JComponent createOptionsPanel() { final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); panel.addCheckbox(InspectionGadgetsBundle.message("method.only.used.from.inner.class.ignore.option"), "ignoreMethodsAccessedFromAnonymousClass"); panel.addCheckbox(InspectionGadgetsBundle.message("ignore.static.methods.accessed.from.a.non.static.inner.class"), "ignoreStaticMethodsFromNonStaticInnerClass"); panel.addCheckbox(InspectionGadgetsBundle.message("only.report.static.methods"), "onlyReportStaticMethods"); return panel; } @Override public BaseInspectionVisitor buildVisitor() { return new MethodOnlyUsedFromInnerClassVisitor(); } private class MethodOnlyUsedFromInnerClassVisitor extends BaseInspectionVisitor { @Override public void visitMethod(PsiMethod method) { super.visitMethod(method); if (!method.hasModifierProperty(PsiModifier.PRIVATE) || method.isConstructor()) { return; } if (onlyReportStaticMethods && !method.hasModifierProperty(PsiModifier.STATIC)) { return; } if (method.getNameIdentifier() == null) { return; } final MethodReferenceFinder processor = new MethodReferenceFinder(method); if (!processor.isOnlyAccessedFromInnerClass()) { return; } final PsiClass containingClass = processor.getContainingClass(); if (ignoreStaticMethodsFromNonStaticInnerClass && method.hasModifierProperty(PsiModifier.STATIC)) { final PsiElement parent = containingClass.getParent(); if (parent instanceof PsiClass && !containingClass.hasModifierProperty(PsiModifier.STATIC)) { return; } } if (containingClass instanceof PsiAnonymousClass) { final PsiClass[] interfaces = containingClass.getInterfaces(); final PsiClass superClass; if (interfaces.length == 1) { superClass = interfaces[0]; registerMethodError(method, superClass, Boolean.valueOf(false)); } else { superClass = containingClass.getSuperClass(); if (superClass == null) { return; } registerMethodError(method, superClass, Boolean.valueOf(true)); } } else { registerMethodError(method, containingClass); } } } private class MethodReferenceFinder implements Processor<PsiReference> { private final PsiClass methodClass; private final PsiMethod method; private boolean onlyAccessedFromInnerClass = false; private PsiClass cache = null; MethodReferenceFinder(@NotNull PsiMethod method) { this.method = method; methodClass = method.getContainingClass(); } @Override public boolean process(PsiReference reference) { final PsiElement element = reference.getElement(); final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); if (method.equals(containingMethod)) { return true; } final PsiClass containingClass = ClassUtils.getContainingClass(element); if (containingClass == null) { onlyAccessedFromInnerClass = false; return false; } if (containingClass instanceof PsiAnonymousClass) { final PsiAnonymousClass anonymousClass = (PsiAnonymousClass)containingClass; final PsiExpressionList argumentList = anonymousClass.getArgumentList(); if (PsiTreeUtil.isAncestor(argumentList, element, true)) { onlyAccessedFromInnerClass = false; return false; } if (ignoreMethodsAccessedFromAnonymousClass) { onlyAccessedFromInnerClass = false; return false; } } if (cache != null) { if (!cache.equals(containingClass)) { onlyAccessedFromInnerClass = false; return false; } } else if (!PsiTreeUtil.isAncestor(methodClass, containingClass, true)) { onlyAccessedFromInnerClass = false; return false; } onlyAccessedFromInnerClass = true; cache = containingClass; return true; } public boolean isOnlyAccessedFromInnerClass() { if (DeclarationSearchUtils.isTooExpensiveToSearch(method, true)) { return false; } ReferencesSearch.search(method).forEach(this); return onlyAccessedFromInnerClass; } public PsiClass getContainingClass() { return cache; } } }