blob: 1286843309cf99cc1b7aabdcc100825e19d36e0b [file] [log] [blame]
/*
* Copyright 2005-2012 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.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.*;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.ClassUtils;
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() {
final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(method.getProject());
final ProgressManager progressManager = ProgressManager.getInstance();
final ProgressIndicator progressIndicator = progressManager.getProgressIndicator();
final PsiSearchHelper.SearchCostResult searchCost =
searchHelper.isCheapEnoughToSearch(method.getName(), method.getResolveScope(), null, progressIndicator);
if (searchCost == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES ||
searchCost == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
return onlyAccessedFromInnerClass;
}
final Query<PsiReference> query = ReferencesSearch.search(method);
query.forEach(this);
return onlyAccessedFromInnerClass;
}
public PsiClass getContainingClass() {
return cache;
}
}
}