// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl;

import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.Strings;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.augment.PsiExtensionMethod;
import com.intellij.psi.impl.java.stubs.PsiClassReferenceListStub;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
import com.intellij.psi.impl.java.stubs.impl.PsiClassStubImpl;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.processor.MethodResolverProcessor;
import com.intellij.psi.search.*;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.*;
import com.intellij.ui.IconManager;
import com.intellij.ui.icons.RowIcon;
import com.intellij.util.*;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBTreeTraverser;
import kotlin.jvm.functions.Function1;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;

import javax.swing.*;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

public final class PsiClassImplUtil {
  private static final Logger LOG = Logger.getInstance(PsiClassImplUtil.class);

  private PsiClassImplUtil() { }

  public static PsiField @NotNull [] getAllFields(@NotNull PsiClass aClass) {
    List<PsiField> map = getAllByMap(aClass, MemberType.FIELD);
    return map.toArray(PsiField.EMPTY_ARRAY);
  }

  public static PsiMethod @NotNull [] getAllMethods(@NotNull PsiClass aClass) {
    List<PsiMethod> methods = getAllByMap(aClass, MemberType.METHOD);
    return methods.toArray(PsiMethod.EMPTY_ARRAY);
  }

  public static PsiClass @NotNull [] getAllInnerClasses(@NotNull PsiClass aClass) {
    List<PsiClass> classes = getAllByMap(aClass, MemberType.CLASS);
    return classes.toArray(PsiClass.EMPTY_ARRAY);
  }

  /**
   * @return a list of all direct super classes of {@param aClass} concatenated with their super classes, recursively up to the top
   */
  public static @NotNull Collection<PsiClass> getAllSuperClassesRecursively(@NotNull PsiClass aClass) {
    return getMap(aClass).myAllSupers;
  }

  public static @Nullable PsiField findFieldByName(@NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.FIELD);
    return byMap.isEmpty() ? null : (PsiField)byMap.get(0);
  }

  public static PsiMethod @NotNull [] findMethodsByName(@NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> methods = findByMap(aClass, name, checkBases, MemberType.METHOD);
    //noinspection SuspiciousToArrayCall
    return methods.toArray(PsiMethod.EMPTY_ARRAY);
  }

  public static @Nullable PsiMethod findMethodBySignature(@NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, boolean checkBases) {
    List<PsiMethod> result = findMethodsBySignature(aClass, patternMethod, checkBases, true);
    return result.isEmpty() ? null : result.get(0);
  }

  // ----------------------------- findMethodsBySignature -----------------------------------

  public static PsiMethod @NotNull [] findMethodsBySignature(@NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, boolean checkBases) {
    List<PsiMethod> methods = findMethodsBySignature(aClass, patternMethod, checkBases, false);
    return methods.toArray(PsiMethod.EMPTY_ARRAY);
  }

  private static @NotNull List<PsiMethod> findMethodsBySignature(@NotNull PsiClass aClass,
                                                                 @NotNull PsiMethod patternMethod,
                                                                 boolean checkBases,
                                                                 boolean stopOnFirst) {
    PsiMethod[] methodsByName = aClass.findMethodsByName(patternMethod.getName(), checkBases);
    if (methodsByName.length == 0) return Collections.emptyList();
    List<PsiMethod> methods = new SmartList<>();
    MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
    for (PsiMethod method : methodsByName) {
      PsiClass superClass = method.getContainingClass();
      PsiSubstitutor substitutor = checkBases && !aClass.equals(superClass) && superClass != null ?
                                         TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY) :
                                         PsiSubstitutor.EMPTY;
      MethodSignature signature = method.getSignature(substitutor);
      if (signature.equals(patternSignature)) {
        methods.add(method);
        if (stopOnFirst) {
          break;
        }
      }
    }
    return methods;
  }

  // ----------------------------------------------------------------------------------------

  public static @Nullable PsiClass findInnerByName(@NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.CLASS);
    return byMap.isEmpty() ? null : (PsiClass)byMap.get(0);
  }

  public static boolean processAllMembersWithoutSubstitutors(@NotNull PsiClass psiClass, @NotNull PsiScopeProcessor processor, @NotNull ResolveState state) {
    ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);

    NameHint nameHint = processor.getHint(NameHint.KEY);
    String name = nameHint == null ? null : nameHint.getName(state);

    if ((classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) &&
        !processMembers(state, processor, getMap(psiClass).getAllMembers(MemberType.METHOD, name))) {
      return false;
    }
    if ((classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) &&
        !processMembers(state, processor, getMap(psiClass).getAllMembers(MemberType.FIELD, name))) {
      return false;
    }
    return (classHint != null && !classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) ||
           processMembers(state, processor, getMap(psiClass).getAllMembers(MemberType.CLASS, name));
  }

  private static boolean processMembers(ResolveState state, PsiScopeProcessor processor, PsiMember @NotNull[] members) {
    for (PsiMember member : members) {
      if (!processor.execute(member, state)) {
        return false;
      }
    }
    return true;
  }

  private static @NotNull List<PsiMember> findByMap(@NotNull PsiClass aClass, String name, boolean checkBases, @NotNull MemberType type) {
    if (name == null) return Collections.emptyList();

    return checkBases
           ? getMap(aClass).calcMembersByName(type, name, null, null)
           : ContainerUtil.filter(type.getMembers(aClass), member -> name.equals(member.getName()));
  }

  public static @Unmodifiable @NotNull <T extends PsiMember> List<Pair<T, PsiSubstitutor>> getAllWithSubstitutorsByMap(@NotNull PsiClass aClass, @NotNull MemberType type) {
    return withSubstitutors(aClass, getMap(aClass).getAllMembers(type, null));
  }

  private static @NotNull <T extends PsiMember> List<T> getAllByMap(@NotNull PsiClass aClass, @NotNull MemberType type) {
    //noinspection unchecked
    return Arrays.asList((T[])getMap(aClass).getAllMembers(type, null));
  }

  public enum MemberType {
    CLASS, FIELD, METHOD;

    PsiMember @NotNull[] getMembers(@NotNull PsiClass aClass) {
      switch (this) {
        case METHOD: return aClass.getMethods();
        case CLASS: return aClass.getInnerClasses();
        default: return aClass.getFields();
      }
    }
  }

  private static MemberCache getMap(@NotNull PsiClass aClass) {
    return getMap(aClass, aClass.getResolveScope());
  }

  private static MemberCache getMap(@NotNull PsiClass aClass, @NotNull GlobalSearchScope scope) {
    return CachedValuesManager.getProjectPsiDependentCache(aClass, c ->
      ConcurrentFactoryMap.createMap((GlobalSearchScope s) -> new MemberCache(c, s))).get(scope);
  }

  private static final class ClassIconRequest {
    private final @NotNull PsiClass psiClass;
    private final int flags;
    private final Icon symbolIcon;

    private ClassIconRequest(@NotNull PsiClass psiClass, int flags, Icon symbolIcon) {
      this.psiClass = psiClass;
      this.flags = flags;
      this.symbolIcon = symbolIcon;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof ClassIconRequest)) return false;

      ClassIconRequest that = (ClassIconRequest)o;

      return flags == that.flags && psiClass.equals(that.psiClass);
    }

    @Override
    public int hashCode() {
      int result = psiClass.hashCode();
      result = 31 * result + flags;
      return result;
    }
  }

  private static final Function1<ClassIconRequest, Icon> FULL_ICON_EVALUATOR = r -> {
    if (!r.psiClass.isValid() || r.psiClass.getProject().isDisposed()) {
      return null;
    }

    boolean isLocked = BitUtil.isSet(r.flags, Iconable.ICON_FLAG_READ_STATUS) && !r.psiClass.isWritable();
    Icon symbolIcon = r.symbolIcon != null
                      ? r.symbolIcon
                      : ElementPresentationUtil.getClassIconOfKind(r.psiClass, ElementPresentationUtil.getClassKind(r.psiClass));
    RowIcon baseIcon =
      IconManager.getInstance().createLayeredIcon(r.psiClass, symbolIcon, ElementPresentationUtil.getFlags(r.psiClass, isLocked));
    Icon result = ElementPresentationUtil.addVisibilityIcon(r.psiClass, r.flags, baseIcon);
    LastComputedIconCache.put(r.psiClass, result, r.flags);
    return result;
  };

  public static Icon getClassIcon(int flags, @NotNull PsiClass aClass) {
    return getClassIcon(flags, aClass, null);
  }

  public static Icon getClassIcon(int flags, @NotNull PsiClass aClass, @Nullable Icon symbolIcon) {
    Icon base = LastComputedIconCache.get(aClass, flags);
    if (base == null) {
      Icon baseSymbolIcon = symbolIcon;
      if (baseSymbolIcon == null) {
        baseSymbolIcon = ElementPresentationUtil.getClassIconOfKind(aClass, ElementPresentationUtil.getBasicClassKind(aClass));
      }
      RowIcon baseIcon = IconManager.getInstance().createLayeredIcon(aClass, baseSymbolIcon, Iconable.ICON_FLAG_FAST_ONLY);
      base = ElementPresentationUtil.addVisibilityIcon(aClass, flags, baseIcon);
    }

    return IconManager.getInstance().createDeferredIcon(base, new ClassIconRequest(aClass, flags, symbolIcon), FULL_ICON_EVALUATOR);
  }

  public static @NotNull SearchScope getClassUseScope(@NotNull PsiClass aClass) {
    if (aClass instanceof PsiAnonymousClass) {
      return new LocalSearchScope(aClass);
    }
    if (aClass instanceof StubBasedPsiElement) {
      StubElement<?> stubElement = ((StubBasedPsiElement<?>)aClass).getStub();
      if (stubElement instanceof PsiClassStub) {
        PsiClassStub<?> stub = (PsiClassStub<?>)stubElement;
        if (stub instanceof PsiClassStubImpl &&
            ((PsiClassStubImpl<?>)stub).isLocalClassInner()) {
          // at least it's smaller than the containing package
          return new LocalSearchScope(aClass.getContainingFile());
        }
      }
    }
    else {
      PsiElement parent = aClass.getParent();
      if (parent instanceof PsiDeclarationStatement) {
        PsiElement grandParent = parent.getParent();
        if (grandParent instanceof PsiCodeBlock) {
          return new LocalSearchScope(grandParent);
          // Actually: The scope of a local class or interface declaration immediately enclosed by a block is the rest
          // of the immediately enclosing block, including the local class or interface declaration itself (jls-6.3).
        }
      }
    }
    GlobalSearchScope maximalUseScope = ResolveScopeManager.getElementUseScope(aClass);
    PsiFile file = aClass.getContainingFile();
    if (PsiImplUtil.isInServerPage(file)) return maximalUseScope;
    SearchScope searchScope = PsiSearchScopeUtil.USE_SCOPE_KEY.get(file);
    if (searchScope != null) return searchScope;
    PsiClass containingClass = aClass.getContainingClass();
    if (containingClass instanceof PsiImplicitClass) {
      return new LocalSearchScope(containingClass);
    }
    if (aClass.hasModifierProperty(PsiModifier.PUBLIC) ||
        aClass.hasModifierProperty(PsiModifier.PROTECTED)) {
      // If the containing class is not final, it's possible to expose nested class through public subclass of containing class 
      return containingClass == null || !containingClass.hasModifierProperty(PsiModifier.FINAL) ? 
             maximalUseScope : containingClass.getUseScope();
    }
    else if (aClass.hasModifierProperty(PsiModifier.PRIVATE) || aClass instanceof PsiTypeParameter) {
      PsiClass topClass = PsiUtil.getTopLevelClass(aClass);
      return new LocalSearchScope(topClass == null ? aClass.getContainingFile() : topClass);
    }
    else {
      PsiPackage aPackage = null;
      if (file instanceof PsiJavaFile) {
        aPackage = JavaPsiFacade.getInstance(aClass.getProject()).findPackage(((PsiJavaFile)file).getPackageName());
      }

      if (aPackage == null) {
        PsiDirectory dir = file.getContainingDirectory();
        if (dir != null) {
          aPackage = JavaDirectoryService.getInstance().getPackage(dir);
        }
      }

      if (aPackage != null) {
        SearchScope scope = PackageScope.packageScope(aPackage, false);
        scope = scope.intersectWith(maximalUseScope);
        VirtualFile virtualFile = file.getVirtualFile();
        if (virtualFile != null && !scope.contains(virtualFile)) {
          // If the current declaration is in a scratch file, it's not included to package scope, so let's add it
          scope = scope.union(new LocalSearchScope(file));
        }
        return scope;
      }

      return new LocalSearchScope(file);
    }
  }

  public static boolean isMainOrPremainMethod(@NotNull PsiMethod method) {
    if ("main".equals(method.getName()) && PsiMethodUtil.isMainMethod(method)) {
      PsiClass psiClass = method.getContainingClass();
      PsiManager manager = method.getManager();
      if (psiClass != null && manager != null) {
        PsiMethod otherMethod = PsiMethodUtil.findMainMethod(psiClass);
        //can be used not from this class
        if (otherMethod == null) return true;
        return manager.areElementsEquivalent(method, otherMethod);
      }
    }
    String name = method.getName();
    if (!("premain".equals(name) || "agentmain".equals(name))) return false;
    if (!PsiTypes.voidType().equals(method.getReturnType())) return false;

    PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject());
    MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY);
    try {
      MethodSignature premain = createSignatureFromText(factory, "void premain(String args, java.lang.instrument.Instrumentation i);");
      if (MethodSignatureUtil.areSignaturesEqual(signature, premain)) return true;
      MethodSignature agentmain = createSignatureFromText(factory, "void agentmain(String args, java.lang.instrument.Instrumentation i);");
      if (MethodSignatureUtil.areSignaturesEqual(signature, agentmain)) return true;
    }
    catch (IncorrectOperationException e) {
      LOG.error(e);
    }

    return false;
  }

  private static @NotNull MethodSignature createSignatureFromText(@NotNull PsiElementFactory factory, @NotNull String text) {
    return factory.createMethodFromText(text, null).getSignature(PsiSubstitutor.EMPTY);
  }

  private static class MemberCache {
    private final @NotNull List<PsiClass> myAllSupers;
    private final ConcurrentMap<MemberType, PsiMember[]> myAllMembers;

    MemberCache(@NotNull PsiClass psiClass, @NotNull GlobalSearchScope scope) {
      boolean dumb = DumbService.isDumb(psiClass.getProject());
      myAllSupers = JBTreeTraverser
        .from((PsiClass c) -> ContainerUtil.mapNotNull(c.getSupers(),
                                                       superOne -> {
                                                         PsiClass correctedClass = PsiSuperMethodUtil.correctClassByScope(superOne, scope);
                                                         if (correctedClass == null && dumb) {
                                                           if (superOne.getContainingFile() instanceof PsiJavaFile &&
                                                               ((PsiJavaFile)superOne.getContainingFile()).getPackageStatement() == null) {
                                                             return superOne;
                                                           }
                                                         }
                                                         return correctedClass;
                                                       }))
        .unique()
        .withRoot(psiClass)
        .toList();
      myAllMembers = ConcurrentFactoryMap.createMap(
        type -> StreamEx.of(myAllSupers).flatArray(type::getMembers).filter(e -> !skipInvalid(e)).toArray(PsiMember.EMPTY_ARRAY));
    }

    @NotNull List<PsiMember> calcMembersByName(@NotNull MemberType type, @NotNull String name, PsiClass psiClass, PsiElement context) {
      List<PsiMember> result = null;
      for (PsiClass eachSuper : myAllSupers) {
        if (type == MemberType.METHOD) {
          PsiMethod[] methods = eachSuper.findMethodsByName(name, false);
          if (methods.length > 0) {
            if (result == null) result = new ArrayList<>();
            Collections.addAll(result, methods);
          }
        }
        else {
          PsiMember member = type == MemberType.CLASS
                             ? eachSuper.findInnerClassByName(name, false)
                             : eachSuper.findFieldByName(name, false);
          if (member != null) {
            if (result == null) result = new ArrayList<>();
            result.add(member);
          }
        }
      }
      if (type == MemberType.METHOD && psiClass != null && context != null) {
        List<PsiExtensionMethod> methods = PsiAugmentProvider.collectExtensionMethods(psiClass, name, context);
        if (!methods.isEmpty()) {
          if (result == null) result = new ArrayList<>();
          result.addAll(methods);
        }
      }
      return result == null ? Collections.emptyList() : ContainerUtil.filter(result, e -> !skipInvalid(e));
    }

    PsiMember[] getAllMembers(@NotNull MemberType type, @Nullable String name) {
      return name == null ? myAllMembers.get(type) : calcMembersByName(type, name, null, null).toArray(PsiMember.EMPTY_ARRAY);
    }
  }

  private static boolean skipInvalid(@NotNull PsiElement element) {
    try {
      PsiUtilCore.ensureValid(element);
    }
    catch (ProcessCanceledException e) {
      throw e;
    }
    catch (Throwable e) {
      LOG.error(e);
      return true;
    }
    return false;
  }

  public static boolean processDeclarationsInClass(@NotNull PsiClass aClass,
                                                   @NotNull PsiScopeProcessor processor,
                                                   @NotNull ResolveState state,
                                                   @Nullable Set<PsiClass> visited,
                                                   PsiElement last,
                                                   @NotNull PsiElement place,
                                                   @NotNull LanguageLevel languageLevel,
                                                   boolean isRaw) {
    return processDeclarationsInClass(aClass, processor, state, visited, last, place, languageLevel, isRaw, place.getResolveScope());
  }

  private static boolean processDeclarationsInClass(@NotNull PsiClass aClass,
                                                    @NotNull PsiScopeProcessor processor,
                                                    @NotNull ResolveState state,
                                                    @Nullable Set<PsiClass> visited,
                                                    PsiElement last,
                                                    @NotNull PsiElement place,
                                                    @NotNull LanguageLevel languageLevel,
                                                    boolean isRaw,
                                                    @NotNull GlobalSearchScope resolveScope) {
    if (last instanceof PsiTypeParameterList || last instanceof PsiModifierList && aClass.getModifierList() == last) {
      return true;
    }
    if (visited != null && visited.contains(aClass)) return true;

    PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
    isRaw = isRaw || PsiUtil.isRawSubstitutor(aClass, substitutor);

    NameHint nameHint = processor.getHint(NameHint.KEY);
    String name = nameHint == null ? null : nameHint.getName(state);
    if (name != null) {
      return processCachedMembersByName(aClass, processor, state, visited, last, place, isRaw, substitutor,
                                        name, languageLevel, resolveScope);
    }
    return processClassMembersWithAllNames(aClass, processor, state, visited, last, place, isRaw, languageLevel, resolveScope);
  }

  private static boolean processCachedMembersByName(@NotNull PsiClass aClass,
                                                    @NotNull PsiScopeProcessor processor,
                                                    @NotNull ResolveState state,
                                                    @Nullable Set<? super PsiClass> visited,
                                                    PsiElement last,
                                                    @NotNull PsiElement place,
                                                    boolean isRaw,
                                                    @NotNull PsiSubstitutor substitutor,
                                                    String name,
                                                    @NotNull LanguageLevel languageLevel,
                                                    @NotNull GlobalSearchScope resolveScope) {
    Function<PsiMember, PsiSubstitutor> finalSubstitutor = new Function<PsiMember, PsiSubstitutor>() {
      final ScopedClassHierarchy hierarchy = ScopedClassHierarchy.getHierarchy(aClass, resolveScope);
      final PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());

      @Override
      public PsiSubstitutor apply(PsiMember member) {
        PsiSubstitutor finalSubstitutor = member.hasModifierProperty(PsiModifier.STATIC) ? substitutor : obtainSubstitutor(member);
        return member instanceof PsiMethod ? checkRaw(isRaw, factory, (PsiMethod)member, finalSubstitutor) : finalSubstitutor;
      }

      private PsiSubstitutor obtainSubstitutor(PsiMember member) {
        PsiClass containingClass = Objects.requireNonNull(member.getContainingClass());
        PsiSubstitutor superSubstitutor = ObjectUtils.notNull(hierarchy.getSuperMembersSubstitutor(containingClass, languageLevel),
                                                              PsiSubstitutor.EMPTY);
        return obtainFinalSubstitutor(containingClass, superSubstitutor, aClass, substitutor, factory, languageLevel);
      }
    };

    ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) {
      PsiField fieldByName = aClass.findFieldByName(name, false);
      if (fieldByName != null) {
        if (!skipInvalid(fieldByName)) {
          processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
          if (!processor.execute(fieldByName, state)) return false;
        }
      }
      else {
        List<PsiMember> list = getMap(aClass, resolveScope).calcMembersByName(MemberType.FIELD, name, aClass, place);
        if (!list.isEmpty()) {
          boolean resolved = false;
          for (PsiMember candidateField : list) {
            PsiClass containingClass = candidateField.getContainingClass();
            if (skipInvalid(candidateField)) {
              continue;
            }
            if (containingClass == null) {
              PsiElement parent = candidateField.getParent();
              LOG.error("No class for field " + candidateField.getName() + " of " + candidateField.getClass() +
                        ", parent " + parent + " of " + (parent == null ? null : parent.getClass()));
              continue;
            }

            processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass);
            if (!processor.execute(candidateField, state.put(PsiSubstitutor.KEY, finalSubstitutor.apply(candidateField)))) {
              resolved = true;
            }
          }
          if (resolved) return false;
        }
      }
    }
    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) {
      if (last != null && last.getContext() == aClass) {
        if (last instanceof PsiClass) {
          if (!processor.execute(last, state)) return false;
        }
        // Parameters
        PsiTypeParameterList list = aClass.getTypeParameterList();
        if (list != null && !list.processDeclarations(processor, state, last, place)) return false;
      }
      if (!(last instanceof PsiReferenceList)) {
        PsiClass classByName = aClass.findInnerClassByName(name, false);
        if (classByName != null) {
          if (!skipInvalid(classByName)) {
            processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
            if (!processor.execute(classByName, state)) return false;
          }
        }
        else {
          List<PsiMember> list = getMap(aClass, resolveScope).calcMembersByName(MemberType.CLASS, name, aClass, place);
          if (!list.isEmpty()) {
            boolean resolved = false;
            for (PsiMember inner : list) {
              if (skipInvalid(inner)) continue;

              PsiClass containingClass = inner.getContainingClass();
              if (containingClass != null) {
                processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass);
                if (!processor.execute(inner, state.put(PsiSubstitutor.KEY, finalSubstitutor.apply(inner)))) {
                  resolved = true;
                }
              }
            }
            if (resolved) return false;
          }
        }
      }
    }
    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
      if (processor instanceof MethodResolverProcessor) {
        MethodResolverProcessor methodResolverProcessor = (MethodResolverProcessor)processor;
        if (methodResolverProcessor.isConstructor()) {
          PsiMethod[] constructors = aClass.getConstructors();
          methodResolverProcessor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
          for (PsiMethod constructor : constructors) {
            if (!skipInvalid(constructor) && !methodResolverProcessor.execute(constructor, state)) return false;
          }
          return true;
        }
      }
      List<PsiMember> list = getMap(aClass, resolveScope).calcMembersByName(MemberType.METHOD, name, aClass, place);
      if (!list.isEmpty()) {
        boolean resolved = false;
        for (PsiMember candidate : list) {
          ProgressIndicatorProvider.checkCanceled();
          PsiMethod candidateMethod = (PsiMethod)candidate;
          if (skipInvalid(candidateMethod)) continue;

          if (processor instanceof MethodResolverProcessor) {
            if (candidateMethod.isConstructor() != ((MethodResolverProcessor)processor).isConstructor()) continue;
          }
          PsiClass containingClass = candidateMethod.getContainingClass();
          if (containingClass == null || visited != null && visited.contains(containingClass)) {
            continue;
          }

          processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass);
          if (!processor.execute(candidateMethod, state.put(PsiSubstitutor.KEY, finalSubstitutor.apply(candidateMethod)))) {
            resolved = true;
          }
        }
        if (resolved) return false;

        if (visited != null) {
          for (PsiMember aList : list) {
            visited.add(aList.getContainingClass());
          }
        }
      }
    }
    return true;
  }

  private static PsiSubstitutor checkRaw(boolean isRaw,
                                         @NotNull PsiElementFactory factory,
                                         @NotNull PsiMethod candidateMethod,
                                         @NotNull PsiSubstitutor substitutor) {
    //4.8-2. Raw Types and Inheritance
    //certain members of a raw type are not erased,
    //namely static members whose types are parameterized, and members inherited from a non-generic supertype.
    if (isRaw && !candidateMethod.hasModifierProperty(PsiModifier.STATIC)) {
      PsiClass containingClass = candidateMethod.getContainingClass();
      if (containingClass != null && containingClass.hasTypeParameters()) {
        PsiTypeParameter[] methodTypeParameters = candidateMethod.getTypeParameters();
        substitutor = factory.createRawSubstitutor(substitutor, methodTypeParameters);
      }
    }
    return substitutor;
  }

  public static PsiSubstitutor obtainFinalSubstitutor(@NotNull PsiClass candidateClass,
                                                      @NotNull PsiSubstitutor candidateSubstitutor,
                                                      @NotNull PsiClass aClass,
                                                      @NotNull PsiSubstitutor substitutor,
                                                      @NotNull PsiElementFactory elementFactory,
                                                      @NotNull LanguageLevel languageLevel) {
    if (PsiUtil.isRawSubstitutor(aClass, substitutor)) {
      return elementFactory.createRawSubstitutor(candidateClass).putAll(substitutor);
    }
    PsiType containingType = elementFactory.createType(candidateClass, candidateSubstitutor, languageLevel);
    PsiType type = substitutor.substitute(containingType);
    if (!(type instanceof PsiClassType)) return candidateSubstitutor;
    return ((PsiClassType)type).resolveGenerics().getSubstitutor();
  }

  private static boolean processClassMembersWithAllNames(@NotNull PsiClass aClass,
                                                         @NotNull PsiScopeProcessor processor,
                                                         @NotNull ResolveState state,
                                                         @Nullable Set<PsiClass> visited,
                                                         PsiElement last,
                                                         @NotNull PsiElement place,
                                                         boolean isRaw,
                                                         @NotNull LanguageLevel languageLevel,
                                                         @NotNull GlobalSearchScope resolveScope) {
    ProgressManager.checkCanceled();
    if (visited == null) {
      visited = new HashSet<>();
    }
    if (!visited.add(aClass)) {
      return true;
    }
    processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
    ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) {
      if (!processMembers(state, processor, aClass.getFields())) return false;
    }

    PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
      PsiSubstitutor baseSubstitutor = state.get(PsiSubstitutor.KEY);
