/*
 * Copyright 2000-2015 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.siyeh.ig.abstraction;

import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.searches.MethodReferencesSearch;
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 com.siyeh.ig.psiutils.TestUtils;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.concurrent.atomic.AtomicReference;

public class StaticMethodOnlyUsedInOneClassInspectionBase extends BaseInspection {

  @SuppressWarnings("PublicField")
  public boolean ignoreTestClasses = false;

  @SuppressWarnings("PublicField")
  public boolean ignoreAnonymousClasses = true;

  @Override
  @NotNull
  public String getDisplayName() {
    return InspectionGadgetsBundle.message("static.method.only.used.in.one.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("static.method.only.used.in.one.class.problem.descriptor.anonymous.extending", name);
      }
      return InspectionGadgetsBundle.message("static.method.only.used.in.one.class.problem.descriptor.anonymous.implementing", name);
    }
    return InspectionGadgetsBundle.message("static.method.only.used.in.one.class.problem.descriptor", name);
  }

  @Nullable
  @Override
  public JComponent createOptionsPanel() {
    final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
    panel.addCheckbox(
      InspectionGadgetsBundle.message("static.method.only.used.in.one.class.ignore.test.option"),
      "ignoreTestClasses"
    );
    panel.addCheckbox(
      InspectionGadgetsBundle.message("static.method.only.used.in.one.class.ignore.anonymous.option"),
      "ignoreAnonymousClasses"
    );
    return panel;
  }

  @Override
  public void writeSettings(@NotNull Element node) throws WriteExternalException {
    writeBooleanOption(node, "ignoreTestClasses");
    writeBooleanOption(node, "ignoreAnonymousClasses", Boolean.TRUE);
  }

  @Override
  public BaseInspectionVisitor buildVisitor() {
    return new StaticMethodOnlyUsedInOneClassVisitor();
  }

  private static class UsageProcessor implements Processor<PsiReference> {

    private final AtomicReference<PsiClass> foundClass = new AtomicReference<PsiClass>();

    @Override
    public boolean process(PsiReference reference) {
      ProgressManager.checkCanceled();
      final PsiElement element = reference.getElement();
      final PsiClass usageClass = ClassUtils.getContainingClass(element);
      if (usageClass == null) {
        return true;
      }
      if (foundClass.compareAndSet(null, usageClass)) {
        return true;
      }
      final PsiClass aClass = foundClass.get();
      final PsiManager manager = usageClass.getManager();
      return manager.areElementsEquivalent(aClass, usageClass);
    }

    /**
     * @return the class the specified method is used from, or null if it is
     *         used from 0 or more than 1 other classes.
     */
    @Nullable
    public PsiClass getUsageClass(final PsiMethod method) {
      final ProgressManager progressManager = ProgressManager.getInstance();
      final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(method.getProject());
      final String name = method.getName();
      final GlobalSearchScope scope = GlobalSearchScope.allScope(method.getProject());
      if (searchHelper.isCheapEnoughToSearch(name, scope, null, progressManager.getProgressIndicator())
          == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
        System.out.println("not cheap");
        return null;
      }
      progressManager.runProcess(new Runnable() {
        @Override
        public void run() {
          final Query<PsiReference> query = MethodReferencesSearch.search(method);
          if (!query.forEach(UsageProcessor.this)) {
            foundClass.set(null);
          }
        }
      }, null);
      return foundClass.get();
    }
  }

  private class StaticMethodOnlyUsedInOneClassVisitor extends BaseInspectionVisitor {

    @Override
    public void visitMethod(PsiMethod method) {
      super.visitMethod(method);
      if (!method.hasModifierProperty(PsiModifier.STATIC)) {
        return;
      }
      if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
        return;
      }
      if (method.getNameIdentifier() == null) {
        return;
      }
      final UsageProcessor usageProcessor = new UsageProcessor();
      final PsiClass usageClass = usageProcessor.getUsageClass(method);
      if (usageClass == null) {
        return;
      }
      final PsiClass containingClass = method.getContainingClass();
      if (usageClass.equals(containingClass)) {
        return;
      }
      if (ignoreTestClasses && TestUtils.isInTestCode(usageClass)) {
        return;
      }
      if (usageClass instanceof PsiAnonymousClass) {
        if (ignoreAnonymousClasses) {
          return;
        }
        if (PsiTreeUtil.isAncestor(containingClass, usageClass, true)) {
          return;
        }
        final PsiClass[] interfaces = usageClass.getInterfaces();
        final PsiClass superClass;
        if (interfaces.length == 1) {
          superClass = interfaces[0];
          registerMethodError(method, superClass, Boolean.FALSE);
        }
        else {
          superClass = usageClass.getSuperClass();
          if (superClass == null) {
            return;
          }
          registerMethodError(method, superClass, Boolean.TRUE);
        }
      }
      else {
        registerMethodError(method, usageClass);
      }
    }
  }
}

@@ -755,65 +755,8 @@
 er;%0A
-import com.intellij.openapi.util.WriteExternalException;%0A
 impo
@@ -774,24 +774,24 @@
 llij.psi.*;%0A
+
 import com.i
@@ -1256,33 +1256,8 @@
 ls;%0A
-import org.jdom.Element;%0A
 impo
@@ -2970,229 +2970,8 @@
  %7D%0A%0A
-  @Override%0A  public void writeSettings(@NotNull Element node) throws WriteExternalException %7B%0A    writeBooleanOption(node, %22ignoreTestClasses%22);%0A    writeBooleanOption(node, %22ignoreAnonymousClasses%22, Boolean.TRUE);%0A  %7D%0A%0A
   @O
@@ -4451,16 +4451,16 @@
 ator())%0A
+
         
@@ -4525,49 +4525,8 @@
 ) %7B%0A
-        System.out.println(%22not cheap%22);%0A
     

fix test