/*
 * Copyright 2000-2014 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.execution;

import com.intellij.execution.junit.JUnitUtil;
import com.intellij.execution.junit.TestClassFilter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.*;
import com.intellij.psi.search.searches.AnnotatedMembersSearch;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Set;

public class ConfigurationUtil {
  // return true if there is JUnit4 test
  public static boolean findAllTestClasses(final TestClassFilter testClassFilter, final Set<PsiClass> found) {
    final PsiManager manager = testClassFilter.getPsiManager();

    final Project project = manager.getProject();
    GlobalSearchScope projectScopeWithoutLibraries = GlobalSearchScope.projectScope(project);
    final GlobalSearchScope scope = projectScopeWithoutLibraries.intersectWith(testClassFilter.getScope());
    ClassInheritorsSearch.search(testClassFilter.getBase(), scope, true, true, false).forEach(new PsiElementProcessorAdapter<PsiClass>(new PsiElementProcessor<PsiClass>() {
      public boolean execute(@NotNull final PsiClass aClass) {
        if (testClassFilter.isAccepted(aClass)) found.add(aClass);
        return true;
      }
    }));

    // classes having suite() method
    final PsiMethod[] suiteMethods = ApplicationManager.getApplication().runReadAction(
        new Computable<PsiMethod[]>() {
          public PsiMethod[] compute() {
            return PsiShortNamesCache.getInstance(project).getMethodsByName(JUnitUtil.SUITE_METHOD_NAME, scope);
          }
        }
    );
    for (final PsiMethod method : suiteMethods) {
      ApplicationManager.getApplication().runReadAction(new Runnable() {
        public void run() {
          final PsiClass containingClass = method.getContainingClass();
          if (containingClass == null) return;
          if (containingClass instanceof PsiAnonymousClass) return;
          if (containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) return;
          if (containingClass.getContainingClass() != null && !containingClass.hasModifierProperty(PsiModifier.STATIC)) return;
          if (JUnitUtil.isSuiteMethod(method) && testClassFilter.isAccepted(containingClass)) {
            found.add(containingClass);
          }
        }
      });
    }

    boolean hasJunit4 = addAnnotatedMethodsAnSubclasses(manager, scope, testClassFilter, found, "org.junit.Test", true);
    hasJunit4 |= addAnnotatedMethodsAnSubclasses(manager, scope, testClassFilter, found, "org.junit.runner.RunWith", false);
    return hasJunit4;
  }

  private static boolean addAnnotatedMethodsAnSubclasses(final PsiManager manager, final GlobalSearchScope scope, final TestClassFilter testClassFilter,
                                                         final Set<PsiClass> found,
                                                         final String annotation,
                                                         final boolean isMethod) {
    final Ref<Boolean> isJUnit4 = new Ref<Boolean>(Boolean.FALSE);
    // annotated with @Test
    final PsiClass testAnnotation = ApplicationManager.getApplication().runReadAction(
        new Computable<PsiClass>() {
          @Nullable
          public PsiClass compute() {
            return JavaPsiFacade.getInstance(manager.getProject()).findClass(annotation, GlobalSearchScope.allScope(manager.getProject()));
          }
        }
    );
    if (testAnnotation != null) {
      //allScope is used to find all abstract test cases which probably have inheritors in the current 'scope'
      AnnotatedMembersSearch.search(testAnnotation, GlobalSearchScope.allScope(manager.getProject())).forEach(new Processor<PsiMember>() {
        public boolean process(final PsiMember annotated) {
          final PsiClass containingClass = annotated instanceof PsiClass ? (PsiClass)annotated : annotated.getContainingClass();
          if (containingClass != null && annotated instanceof PsiMethod == isMethod) {
            if (ApplicationManager.getApplication().runReadAction(
              new Computable<Boolean>() {
                @Override
                public Boolean compute() {
                  final VirtualFile file = PsiUtilCore.getVirtualFile(containingClass);
                  return file != null && scope.contains(file) && testClassFilter.isAccepted(containingClass);
                }
              })) {
              found.add(containingClass);
              isJUnit4.set(Boolean.TRUE);
            }
            ClassInheritorsSearch.search(containingClass, scope, true, true, false)
              .forEach(new PsiElementProcessorAdapter<PsiClass>(new PsiElementProcessor<PsiClass>() {
                public boolean execute(@NotNull final PsiClass aClass) {
                  if (testClassFilter.isAccepted(aClass)) {
                    found.add(aClass);
                    isJUnit4.set(Boolean.TRUE);
                  }
                  return true;
                }
              }));
          }
          return true;
        }
      });
    }

    return isJUnit4.get().booleanValue();
  }
}

@@ -4816,37 +4816,260 @@
 d : 
-annotated.getContainingClass(
+ApplicationManager.getApplication()%0A            .runReadAction(new Computable%3CPsiClass%3E() %7B%0A                @Override%0A                public PsiClass compute() %7B%0A                  return annotated.getContainingClass();%0A                %7D%0A              %7D
 );%0A 

read action