// 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.codeInspection.reference;

import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.VisibilityUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;
import org.jetbrains.uast.visitor.AbstractUastVisitor;

import java.util.*;

public final class RefJavaUtilImpl extends RefJavaUtil {
  private static final Logger LOG = Logger.getInstance(RefJavaUtilImpl.class);

  @Override
  public void addReferences(@NotNull PsiModifierListOwner psiFrom, @NotNull RefJavaElement ref, @Nullable PsiElement findIn) {
    UDeclaration decl = UastContextKt.toUElement(psiFrom, UDeclaration.class);
    UElement uFindIn = UastContextKt.toUElement(findIn);
    if (decl != null && findIn != null) {
      addReferencesTo(decl, ref, uFindIn);
    }
  }

  @Override
  public void addReferencesTo(@NotNull UElement decl, @NotNull RefJavaElement ref, UElement @Nullable ... findIn) {
    final RefJavaElementImpl refFrom = (RefJavaElementImpl)ref;
    final RefManagerImpl refManager = refFrom.getRefManager();
    if (findIn == null) {
      return;
    }
    for (UElement element : findIn) {
      if (element == null) continue;
      element.accept(new AbstractUastVisitor() {
                       @Override
                       public boolean visitEnumConstant(@NotNull UEnumConstant node) {
                         processNewLikeConstruct(node.resolve(), node);
                         return false;
                       }

                       @Override
                       public boolean visitNamedExpression(@NotNull UNamedExpression node) {
                         PsiElement source = node.getSourcePsi();
                         if (source instanceof PsiNameValuePair pair) {
                           PsiReference reference = pair.getReference();
                           if (reference != null) {
                             PsiElement target = reference.resolve();
                             final RefElement refElement = refManager.getReference(target);
                             if (refElement != null) {
                               refElement.initializeIfNeeded();
                               refFrom.addReference(refElement, target, decl, false, true, node);
                             }
                           }
                         }
                         return false;
                       }

                       @Override
                       public boolean visitAnnotation(@NotNull UAnnotation node) {
                         PsiClass javaClass = node.resolve();
                         if (javaClass != null) {
                           final RefElement refClass = refManager.getReference(javaClass.getOriginalElement());
                           if (refClass != null) refClass.initializeIfNeeded();
                           refFrom.addReference(refClass, javaClass.getOriginalElement(), decl, false, true, null);
                           checkRepeatableAnnotation(javaClass, node);
                         }
                         return false;
                       }

                       private void checkRepeatableAnnotation(PsiClass annotationClass, UAnnotation annotation) {
                         if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(annotationClass.getQualifiedName())) return;
                         UExpression value = annotation.findAttributeValue("value");
                         while (value instanceof UParenthesizedExpression parenth) {
                           value = parenth.getExpression();
                         }
                         if (!(value instanceof UClassLiteralExpression classLiteralExpression)) return;
                         PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(classLiteralExpression.getType());
                         if (aClass == null) return;
                         if (!(refManager.getReference(aClass.getOriginalElement()) instanceof RefClassImpl refClass)) return;
                         refClass.initializeIfNeeded();
                         for (RefEntity child : refClass.getChildren()) {
                           if (child instanceof RefMethod method && "value()".equals(method.getName())) {
                             refFrom.addReference(method, null, decl, false, true, null);
                             break;
                           }
                         }
                       }

                       @Override
                       public boolean visitTypeReferenceExpression(@NotNull UTypeReferenceExpression node) {
                         PsiType type = node.getType();
                         visitTypeRefs(type);
                         return false;
                       }

                       private void visitTypeRefs(PsiType type) {
                         type = type.getDeepComponentType();
                         if (type instanceof PsiClassType) {
                           type.accept(new PsiTypeVisitor<Void>() {
                             @Override
                             public Void visitClassType(@NotNull PsiClassType classType) {
                               for (PsiType parameter : classType.getParameters()) {
                                 parameter.accept(this);
                               }
                               PsiClass aClass = classType.resolve();
                               UClass target = UastContextKt.toUElement(aClass, UClass.class);
                               if (target != null) {
                                 final RefElement refElement = refManager.getReference(target.getSourcePsi());
                                 if (refElement != null) refElement.initializeIfNeeded();
                                 refFrom.addReference(refElement, aClass, decl, false, true, null);
                               }
                               return null;
                             }
                           });
                         }
                       }

                       @Override
                       public boolean visitVariable(@NotNull UVariable node) {
                         visitTypeRefs(node.getType());
                         return false;
                       }

                       @Override
                       public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitLiteralExpression(@NotNull ULiteralExpression node) {
                         PsiElement sourcePsi = node.getSourcePsi();
                         if (sourcePsi != null) {
                           for (PsiReference reference : sourcePsi.getReferences()) {
                             PsiElement resolve = reference.resolve();
                             if (resolve instanceof PsiMember) {
                               final RefElement refResolved = refManager.getReference(resolve);
                               if (refResolved != null) refResolved.initializeIfNeeded();
                               refFrom.addReference(refResolved, resolve, decl, false, true, null);
                               if (refResolved instanceof RefMethodImpl refMethod) {
                                 updateRefMethod(resolve, refMethod, node, decl);
                               }
                             }
                           }
                         }
                         return false;
                       }

                       @Override
                       public boolean visitPrefixExpression(@NotNull UPrefixExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitPostfixExpression(@NotNull UPostfixExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitUnaryExpression(@NotNull UUnaryExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitBinaryExpression(@NotNull UBinaryExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitQualifiedReferenceExpression(@NotNull UQualifiedReferenceExpression node) {
                         visitReferenceExpression(node);
                         return false;
                       }

                       @Override
                       public boolean visitObjectLiteralExpression(@NotNull UObjectLiteralExpression node) {
                         visitReferenceExpression(node);
                         visitClass(node.getDeclaration());
                         return false;
                       }

                       @Override
                       public boolean visitCallExpression(@NotNull UCallExpression node) {
                         visitReferenceExpression(node);
                         if (node instanceof UObjectLiteralExpression objectLiteralExpression) {
                           visitClass(objectLiteralExpression.getDeclaration());
                         }
                         if (node.getKind() == UastCallKind.CONSTRUCTOR_CALL) {
                           PsiElement resolvedMethod = returnToPhysical(node.resolve());
                           processNewLikeConstruct(resolvedMethod, node);
                         }
                         try {
                           node.getTypeArguments().forEach(this::visitTypeRefs);
                         }
                         catch (UnsupportedOperationException e) {
                           //TODO happens somewhere in kotlin plugin. Please assign those exception for Dmitry Batkovich
                           LOG.error(e);
                         }
                         return false;
                       }

                       private void visitReferenceExpression(@NotNull UExpression node) {
                         UElement uastParent = node.getUastParent();
                         if (uastParent instanceof UQualifiedReferenceExpression qualifiedReference && qualifiedReference.getSelector() == node) {
                           return;
                         }
                         PsiElement psiResolved = null;
                         if (node instanceof UCallExpression callExpression &&
                             "invoke".equals(callExpression.getMethodName()) &&
                             callExpression.getReceiver() instanceof UResolvable resolvable) {
                           psiResolved = resolvable.resolve();
                         }
                         else if (node instanceof UResolvable resolvable) {
                           psiResolved = resolvable.resolve();
                         }
                         else if (node instanceof UBinaryExpression binaryExpression) {
                           psiResolved = binaryExpression.resolveOperator();
                         }
                         else if (node instanceof UUnaryExpression unaryExpression) {
                           psiResolved = unaryExpression.resolveOperator();
                         }

                         psiResolved = returnToPhysical(psiResolved);
                         RefElement refResolved = refManager.getReference(psiResolved);
                         boolean writing = isAccessedForWriting(node);
                         boolean reading = isAccessedForReading(node);
                         if (refResolved != null) refResolved.initializeIfNeeded();
                         refFrom.addReference(refResolved, psiResolved, decl, writing, reading, node);

                         if (refResolved instanceof RefMethodImpl refMethod) {
                           updateRefMethod(psiResolved, refMethod, node, decl);
                         }
                         else if (refResolved instanceof RefField) {
                           if (node instanceof UResolvable resolvable) {
                             UMethod uProperty = UastContextKt.toUElement(resolvable.resolve(), UMethod.class);
                             if (uProperty != null) {
                             //  PsiMethod property = uProperty.getJavaPsi();
                             //  RefElement refProperty = refManager.getReference(uProperty);
                             //  if (refProperty != null) {
                             //    refProperty.waitForInitialized();
                             //    refFrom.addReference(refProperty, property, decl, false, true, node);
                             //  }
                             }
                           }
                         }
                         else if (refResolved ==  null && psiResolved instanceof PsiMethod method) {
                           markEnumUsedIfValuesMethod(method, node);
                         }

                         if (psiResolved instanceof PsiMember psiMember) {
                           //TODO support kotlin
                           addClassReferenceForStaticImport(node, psiMember, refFrom, decl);
                         }
                       }

                       @Override
                       public boolean visitLambdaExpression(@NotNull ULambdaExpression lambda) {
                         processFunctionalExpression(lambda, lambda.getFunctionalInterfaceType());
                         return true;
                       }

                       private void markEnumUsedIfValuesMethod(PsiMethod psiWhat, UExpression expression) {
                         //TODO support kotlin enums
                         final PsiClass containingClass = psiWhat.getContainingClass();
                         if (containingClass == null || !containingClass.isEnum()) {
                           return;
                         }
                         String methodName = psiWhat.getName();
                         if (!"values".equals(methodName) && !"valueOf".equals(methodName)) {
                           return;
                         }
                         for (PsiField enumConstant : containingClass.getFields()) {
                           if (enumConstant instanceof PsiEnumConstant) {
                             final RefJavaElementImpl enumConstantReference = (RefJavaElementImpl)refFrom.getRefManager().getReference(enumConstant);
                             if (enumConstantReference != null) {
                               refFrom.addOutReference(enumConstantReference);
                               enumConstantReference.markReferenced(refFrom, false, true, expression);
                             }
                           }
                         }
                       }

                       @Override
                       public boolean visitCallableReferenceExpression(@NotNull UCallableReferenceExpression methodRef) {
                         UExpression qualifierExpression = methodRef.getQualifierExpression();
                         if (qualifierExpression != null) {
                           qualifierExpression.accept(this);
                         }
                         RefElement refMethod = refManager.getReference(methodRef.getSourcePsi());
                         if (refFrom == refMethod) {
                           visitReferenceExpression(methodRef);
                           return false;
                         }
                         else {
                           processFunctionalExpression(methodRef, getFunctionalInterfaceType(methodRef));
                           return true;
                         }
                       }

                       private void processFunctionalExpression(@NotNull UExpression expression, @Nullable PsiType type) {
                         PsiElement aClass = PsiUtil.resolveClassInType(type);
                         if (aClass != null) {
                           aClass = ((PsiClass)aClass).getSourceElement();
                         }
                         if (aClass != null) {
                           final RefElement refWhat = refManager.getReference(aClass);
                           if (refWhat != null) refWhat.initializeIfNeeded();
                           refFrom.addReference(refWhat, aClass, decl, false, true, null);
                         }
                         PsiElement functionalExpr = expression.getSourcePsi();
                         RefElement refFunctionalExpr = refManager.getReference(functionalExpr);
                         if (refFunctionalExpr != null) refFunctionalExpr.initializeIfNeeded();
                         refFrom.addReference(refFunctionalExpr, functionalExpr, decl, false, true, expression);
                       }

                       private void processNewLikeConstruct(PsiElement javaConstructor, UCallExpression call) {
                         if (javaConstructor != null
                             && refManager.getReference(javaConstructor.getOriginalElement()) instanceof RefMethodImpl refConstructor) {
                           refConstructor.initializeIfNeeded();
                           refFrom.addReference(refConstructor, javaConstructor, decl, false, true, call);
                           refConstructor.updateParameterValues(call, javaConstructor);
                         }
                         else {
                           UReferenceExpression reference = call.getClassReference();
                           if (reference != null) {
                             PsiElement constructorClass = reference.resolve();
                             if (constructorClass instanceof PsiClass psiClass) {
                               processClassReference(psiClass, true, call);
                             }
                           }
                         }
                         for (UExpression arg : call.getValueArguments()) {
                           arg.accept(this);
                         }
                       }

                       @Override
                       public boolean visitClass(@NotNull UClass uClass) {
                         for (UTypeReferenceExpression type : uClass.getUastSuperTypes()) {
                           type.accept(this);
                         }
                         PsiElement sourcePsi = uClass.getSourcePsi();
                         RefElement refWhat = refManager.getReference(sourcePsi);
                         if (refWhat != null) refWhat.initializeIfNeeded();
                         refFrom.addReference(refWhat, sourcePsi, decl, false, true, null);
                         return true;
                       }

                       @Override
                       public boolean visitReturnExpression(@NotNull UReturnExpression node) {
                         RefMethodImpl refMethod = null;
                         if (refFrom instanceof RefMethodImpl &&
                             UastUtils.getParentOfType(node, UMethod.class, false, UClass.class, ULambdaExpression.class) == decl) {
                           refMethod = (RefMethodImpl)refFrom;
                         }
                         else if (refFrom instanceof RefFunctionalExpression) {
                           UElement target = node.getJumpTarget();
                           if (target instanceof UMethod) {
                             refMethod = refManager.getReference(target.getSourcePsi()) instanceof RefMethodImpl m ? m : null;
                           }
                           else if (decl instanceof ULambdaExpression lambdaExpression) {
                             PsiMethod lambdaMethod = LambdaUtil.getFunctionalInterfaceMethod(lambdaExpression.getFunctionalInterfaceType());
                             refMethod = refManager.getReference(lambdaMethod) instanceof RefMethodImpl m ? m : null;
                           }
                         }
                         if (refMethod != null) {
                           refMethod.initializeIfNeeded();
                           refMethod.updateReturnValueTemplate(node.getReturnExpression());
                         }
                         return false;
                       }

                       @Override
                       public boolean visitClassLiteralExpression(@NotNull UClassLiteralExpression node) {
                         processClassReference(PsiUtil.resolveClassInClassTypeOnly(node.getType()), false, node);
                         return false;
                       }

                       private void processClassReference(PsiClass psiClass, boolean defaultConstructorOnly, UExpression node) {
                         if (psiClass != null) {
                           if (refManager.getReference(psiClass.getNavigationElement()) instanceof RefClassImpl refClass) {
                             boolean hasConstructorsMarked = false;
                             refClass.initializeIfNeeded();

                             if (defaultConstructorOnly) {
                               WritableRefElement refDefaultConstructor = (WritableRefElement)refClass.getDefaultConstructor();
                               if (refDefaultConstructor != null) {
                                 refDefaultConstructor.addInReference(refFrom);
                                 refFrom.addOutReference(refDefaultConstructor);
                                 hasConstructorsMarked = true;
                               }
                             }
                             else {
                               for (RefMethod cons : refClass.getConstructors()) {
                                 if (cons instanceof RefImplicitConstructor) continue;
                                 ((WritableRefElement)cons).addInReference(refFrom);
                                 refFrom.addOutReference(cons);
                                 hasConstructorsMarked = true;
                               }

                               if (refClass.isEnum()) {
                                 for (RefField field : refClass.getFields()) {
                                   if (field.isEnumConstant()) {
                                     ((RefFieldImpl)field).markReferenced(refFrom, false, true, node);
                                     refFrom.addOutReference(field);
                                   }
                                 }
                               }
                             }

                             if (!hasConstructorsMarked) {
                               refFrom.addReference(refClass, psiClass, decl, false, true, node);
                             }
                           }
                         }
                       }
                     }
      );
    }
  }

  private static void addClassReferenceForStaticImport(UExpression node,
                                                       PsiMember psiResolved,
                                                       RefJavaElementImpl refFrom, UElement decl) {
    PsiElement sourcePsi = node.getSourcePsi();
    if (sourcePsi instanceof PsiReferenceExpression ref) {
      JavaResolveResult result = ref.advancedResolve(false);
      if (result.getCurrentFileResolveScope() instanceof PsiImportStaticStatement) {
        final PsiClass containingClass = psiResolved.getContainingClass();
        if (containingClass != null) {
          RefElement refContainingClass = refFrom.getRefManager().getReference(containingClass);
          if (refContainingClass != null) {
            refContainingClass.initializeIfNeeded();
            refFrom.addReference(refContainingClass, containingClass, decl, false, true, node);
          }
        }
      }
    }
  }

  private void updateRefMethod(PsiElement psiResolved,
                               RefMethodImpl refMethod,
                               @NotNull UExpression uExpression,
                               UElement uFrom) {
    UMethod uMethod = Objects.requireNonNull(UastContextKt.toUElement(psiResolved, UMethod.class));
    refMethod.initializeIfNeeded();
    if (uExpression instanceof UCallableReferenceExpression callableReference) {
      PsiType returnType = uMethod.getReturnType();
      if (!uMethod.isConstructor()) {
        final PsiType type = getFunctionalInterfaceType(callableReference);
        if (!PsiTypes.voidType().equals(LambdaUtil.getFunctionalInterfaceReturnType(type))) {
          refMethod.setReturnValueUsed(true);
          addTypeReference(uFrom, returnType, refMethod.getRefManager());
        }
      }
      refMethod.setParametersAreUnknown();
      return;
    }
    if (uExpression instanceof ULiteralExpression) { //references in literal expressions
      PsiType returnType = uMethod.getReturnType();
      if (!uMethod.isConstructor() && !PsiTypes.voidType().equals(returnType)) {
        refMethod.setReturnValueUsed(true);
        addTypeReference(uFrom, returnType, refMethod.getRefManager());
      }
      return;
    }

    PsiType returnType = uMethod.getReturnType();
    if (!uMethod.isConstructor() && !PsiTypes.voidType().equals(returnType)) {
      if (!(uExpression.getJavaPsi() instanceof PsiExpression expression) || !ExpressionUtils.isVoidContext(expression)) {
        refMethod.setReturnValueUsed(true);
      }

      addTypeReference(uFrom, returnType, refMethod.getRefManager());
    }

    UCallExpression call = null;
    if (uExpression instanceof UCallExpression callExpression) {
      call = callExpression;
    }
    else if (uExpression instanceof UQualifiedReferenceExpression qualifiedReference) {
      call = qualifiedReference.getSelector() instanceof UCallExpression c ? c : null;
    }
    if (call != null) {
      refMethod.updateParameterValues(call, psiResolved);

      final PsiType usedType = call.getReceiverType();
      if (usedType != null) {
        UClass containingClass = UDeclarationKt.getContainingDeclaration(uMethod, UClass.class);
        if (containingClass != null) {
          final String fqName = containingClass.getQualifiedName();
          if (fqName != null) {
            final Project project = psiResolved.getProject();
            final PsiClassType methodOwnerType = JavaPsiFacade.getElementFactory(project)
              .createTypeByFQClassName(fqName, GlobalSearchScope.allScope(project));
            if (!usedType.equals(methodOwnerType)) {
              refMethod.setCalledOnSubClass(true);
            }
          }
        }
      }
    }
  }

  private static PsiType getFunctionalInterfaceType(@NotNull UCallableReferenceExpression expression) {
    PsiElement psi = expression.getSourcePsi();
    if (psi instanceof PsiFunctionalExpression functionalExpression) {
      return functionalExpression.getFunctionalInterfaceType();
    }
    return null;
  }

  @Override
  public RefClass getTopLevelClass(@NotNull RefElement refElement) {
    LOG.assertTrue(refElement.isInitialized(), refElement.getName() + " not initialized");
    RefEntity refParent = refElement.getOwner();

    while (refParent instanceof RefElement && !(refParent instanceof RefFile)) {
      refElement = (RefElement)refParent;
      refParent = refParent.getOwner();
    }

    return refElement instanceof RefClass refClass ? refClass : null;
  }

  @Override
  public boolean isInheritor(@NotNull RefClass subClass, RefClass superClass) {
    if (subClass == superClass) return true;
    LOG.assertTrue(subClass.isInitialized());

    for (RefClass baseClass : subClass.getBaseClasses()) {
      if (isInheritor(baseClass, superClass)) return true;
    }

    return false;
  }

  @Override
  public @Nullable String getPackageName(RefEntity refEntity) {
    if (refEntity instanceof RefProject || refEntity instanceof RefJavaModule) {
      return null;
    }
    if (!(refEntity instanceof RefJavaElement) && !(refEntity instanceof RefPackage) && !(refEntity instanceof RefJavaFileImpl)) {
      return null;
    }
    RefPackage refPackage = getPackage(refEntity);

    return refPackage == null ? JavaAnalysisBundle.message("inspection.reference.default.package") : refPackage.getQualifiedName();
  }

  @Override
  public @NotNull String getAccessModifier(@NotNull PsiModifierListOwner psiElement) {
    if (psiElement instanceof PsiParameter) return PsiModifier.PACKAGE_LOCAL;

    PsiModifierList list = psiElement.getModifierList();
    String result = PsiModifier.PACKAGE_LOCAL;

    if (list != null) {
      if (list.hasModifierProperty(PsiModifier.PRIVATE)) {
        result = PsiModifier.PRIVATE;
      }
      else if (list.hasModifierProperty(PsiModifier.PROTECTED)) {
        result = PsiModifier.PROTECTED;
      }
      else if (list.hasModifierProperty(PsiModifier.PUBLIC)) {
        result = PsiModifier.PUBLIC;
      }
      else if (psiElement.getParent() instanceof PsiClass ownerClass && ownerClass.isInterface()) {
        result = PsiModifier.PUBLIC;
      }
    }

    return result;
  }

  @Override
  public @Nullable RefClass getOwnerClass(RefManager refManager, UElement uElement) {
    while (uElement != null && !(uElement instanceof UClass)) {
      uElement = uElement.getUastParent();
    }

    if (uElement != null) {
      RefElement reference = refManager.getReference(uElement.getSourcePsi());
      return reference instanceof RefClass refClass ? refClass : null;
    }

    return null;
  }

  @Override
  public @Nullable RefClass getOwnerClass(RefElement refElement) {
    LOG.assertTrue(refElement.isInitialized(), refElement.getName() + " not initialized");
    RefEntity parent = refElement.getOwner();

    while (!(parent instanceof RefClass) && parent instanceof RefElement) {
      LOG.assertTrue(((RefElement)parent).isInitialized());
      parent = parent.getOwner();
    }

    return parent instanceof RefClass refClass ? refClass : null;
  }


  @Override
  public boolean isMethodOnlyCallsSuper(UMethod method) {
    PsiMethod javaMethod = method.getJavaPsi();
    boolean hasStatements = false;
    UExpression body = method.getUastBody();
    if (body != null) {
      List<UExpression> statements =
        body instanceof UBlockExpression blockExpression ? blockExpression.getExpressions() : Collections.singletonList(body);
      if (statements.size() > 1) return false;
      for (UExpression expression : statements) {
        boolean isCallToSameSuper = false;
        if (expression instanceof UReturnExpression returnExpression) {
          UExpression returnExpr = returnExpression.getReturnExpression();
          isCallToSameSuper = returnExpr == null || isCallToSuperMethod(returnExpr, method);
        }
        else if (!(expression instanceof UBlockExpression)) {
          isCallToSameSuper = isCallToSuperMethod(expression, method);
        }

        hasStatements = true;
        if (isCallToSameSuper) continue;
        return false;
      }
    }

    if (hasStatements) {
      final PsiMethod[] superMethods = javaMethod.findSuperMethods();
      for (PsiMethod superMethod : superMethods) {
        if (VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(superMethod.getModifierList()),
                                   VisibilityUtil.getVisibilityModifier(javaMethod.getModifierList())) > 0) {
          return false;
        }
      }
      PsiClass aClass = javaMethod.getContainingClass();
      if (aClass == null || hasUnrelatedDefaults(aClass, Arrays.asList(superMethods))) {
        return false;
      }
    }
    return hasStatements;
  }

  @Override
  public boolean isCallToSuperMethod(UExpression expression, UMethod method) {
    if (expression instanceof UQualifiedReferenceExpression qualifiedReference) {
      UExpression receiver = qualifiedReference.getReceiver();
