/*
 * Copyright (c) 2005 Your Corporation. All Rights Reserved.
 */
package com.intellij.psi.search.searches;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchScopeUtil;
import com.intellij.psi.search.SearchScope;
import com.intellij.util.*;
import gnu.trove.THashSet;

import java.util.Collection;

/**
 * @author max
 */
public class ClassInheritorsSearch extends ExtensibleQueryFactory<PsiClass, ClassInheritorsSearch.SearchParameters> {
  private static final Logger LOG = Logger.getInstance("#com.intellij.psi.search.searches.ClassInheritorsSearch");

  public static ClassInheritorsSearch INSTANCE = new ClassInheritorsSearch();

  static {
    INSTANCE.registerExecutor(new QueryExecutor<PsiClass, SearchParameters>() {
      public boolean execute(final SearchParameters p, final Processor<PsiClass> consumer) {
        final PsiClass baseClass = p.getClassToProcess();
        final SearchScope searchScope = p.getScope();

        LOG.assertTrue(searchScope != null);

        ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
        if (progress != null) {
          progress.pushState();
          String className = baseClass.getName();
          progress.setText(className != null ?
                           PsiBundle.message("psi.search.inheritors.of.class.progress", className) :
                           PsiBundle.message("psi.search.inheritors.progress"));
        }

        Collection<PsiClass> processed = new THashSet<PsiClass>();
        processed.add(baseClass);
        boolean result = processInheritors(consumer,
                                           baseClass,
                                           searchScope,
                                           p.isCheckDeep(),
                                           processed,
                                           p.isCheckInheritance());

        if (progress != null) {
          progress.popState();
        }

        return result;
      }
    });
  }

  public static class SearchParameters {
    private final PsiClass myClass;
    private final SearchScope myScope;
    private final boolean myCheckDeep;
    private final boolean myCheckInheritance;

    public SearchParameters(final PsiClass aClass, SearchScope scope, final boolean checkDeep, final boolean checkInheritance) {
      myClass = aClass;
      myScope = scope;
      myCheckDeep = checkDeep;
      myCheckInheritance = checkInheritance;
    }

    public PsiClass getClassToProcess() {
      return myClass;
    }

    public boolean isCheckDeep() {
      return myCheckDeep;
    }

    public SearchScope getScope() {
      return myScope;
    }

    public boolean isCheckInheritance() {
      return myCheckInheritance;
    }
  }

  private ClassInheritorsSearch() {}

  public static Query<PsiClass> search(final PsiClass aClass, SearchScope scope, final boolean checkDeep, final boolean checkInheritance) {
    return INSTANCE.createUniqueResultsQuery(new SearchParameters(aClass, scope, checkDeep, checkInheritance));
  }

  public static Query<PsiClass> search(final PsiClass aClass, SearchScope scope, final boolean checkDeep) {
    return search(aClass, scope, checkDeep, true);
  }

  public static Query<PsiClass> search(final PsiClass aClass, final boolean checkDeep) {
    return search(aClass, aClass.getUseScope(), checkDeep);
  }

  public static Query<PsiClass> search(final PsiClass aClass) {
    return search(aClass, true);
  }

  private static boolean processInheritors(final Processor<PsiClass> consumer,
                                           final PsiClass baseClass,
                                           final SearchScope searchScope,
                                           final boolean checkDeep,
                                           final Collection<PsiClass> processed,
                                           final boolean checkInheritance) {
    LOG.assertTrue(searchScope != null);

    if (baseClass instanceof PsiAnonymousClass) return true;
    if (baseClass.hasModifierProperty(PsiModifier.FINAL)) return true;

    if ("java.lang.Object".equals(baseClass.getQualifiedName())) { // special case
      // TODO!
    }

    final PsiManager psiManager = PsiManager.getInstance(baseClass.getProject());

    DirectClassInheritorsSearch.search(baseClass).forEach(new Processor<PsiClass>() {
      public boolean process(final PsiClass candidate) {
        final Ref<Boolean> result = new Ref<Boolean>();
        ApplicationManager.getApplication().runReadAction(new Runnable() {
          public void run() {
            if (checkInheritance || (checkDeep && !(candidate instanceof PsiAnonymousClass))) {
              if (!candidate.isInheritor(baseClass, false) || !processed.add(candidate)) {
                result.set(true);
                return;
              }
            }

            if (PsiSearchScopeUtil.isInScope(searchScope, candidate)) {
              if (candidate instanceof PsiAnonymousClass) {
                result.set(consumer.process(candidate));
              }
              else {
                if (searchScope instanceof GlobalSearchScope) {
                  String qName = candidate.getQualifiedName();
                  if (qName != null) {
                    PsiClass[] candidateClasses = psiManager.findClasses(qName, (GlobalSearchScope)searchScope);
                    if (ArrayUtil.find(candidateClasses, candidate) == -1) result.set(true);
                  }
                }
                if (!consumer.process(candidate)) result.set(false);
              }
            }
          }
        });
        if (!result.isNull()) return result.get();

        if (checkDeep) {
          if (!processInheritors(consumer, candidate, searchScope, checkDeep, processed, checkInheritance)) return false;
        }

        return true;
      }
    });


    return true;
  }
}

@@ -104,16 +104,76 @@
 rches;%0A%0A
+import com.intellij.openapi.application.ApplicationManager;%0A
 import c
@@ -207,24 +207,24 @@
 tic.Logger;%0A
-
 import com.i
@@ -353,38 +353,23 @@
 api.
-application.ApplicationManager
+util.Computable
 ;%0Aim
@@ -601,17 +601,133 @@
 ij.util.
-*
+ArrayUtil;%0Aimport com.intellij.util.Processor;%0Aimport com.intellij.util.Query;%0Aimport com.intellij.util.QueryExecutor
 ;%0Aimport
@@ -4488,64 +4488,271 @@
 ue;%0A
+%0A
     
-if (baseClass.hasModifierProperty(PsiModifier.FINAL)
+boolean isFinal = ApplicationManager.getApplication().runReadAction(new Computable%3CBoolean%3E() %7B%0A      public Boolean compute() %7B%0A        return Boolean.valueOf(baseClass.hasModifierProperty(PsiModifier.FINAL));%0A      %7D%0A    %7D).booleanValue();%0A%0A    if (isFinal
 ) re
@@ -4759,24 +4759,36 @@
 turn true;%0A%0A
+    /* TODO%0A
     if (%22jav
@@ -4874,24 +4874,31 @@
  TODO!%0A    %7D
+%0A    */
 %0A%0A    final 

Fix read access assertion in groovy plugin